├── .gitignore ├── LICENSE.txt ├── PRET.gif ├── README.md ├── blinkinterp.m ├── hanning.m ├── pret_batch_process.m ├── pret_bootstrap.m ├── pret_bootstrap_sj.m ├── pret_calc.m ├── pret_cost.m ├── pret_default_options.m ├── pret_estimate.m ├── pret_estimate_sj.m ├── pret_fake_data.m ├── pret_generate_params.m ├── pret_model.m ├── pret_model_check.m ├── pret_optim.m ├── pret_plot_boots.m ├── pret_plot_model.m ├── pret_preprocess.m ├── pret_sample_script.m └── pupilrf.m /.gitignore: -------------------------------------------------------------------------------- 1 | #Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | *.m~ 40 | *~ 41 | Icon? -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /PRET.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobaparker/PRET/c2769ece8513dfbebd3067a90f11f5038b4aebf8/PRET.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PRET # 2 | __Pupil Response Estimation Toolbox (PRET)__ 3 | 4 | by Jacob Parker and Rachel Denison 5 | 6 | 7 | 8 | Welcome to the Pupil Response Estimation Toolbox (PRET)! This is a freely available, Matlab toolbox for analyzing pupillometry 9 | data by modeling the pupil size time series as a linear combination of pupil responses to discrete events occurring over time. 10 | The functions in this toolbox can be used to implement the analysis described in Denison, Parker, and Carrasco[1], which builds upon the 11 | paradigm created by Hoeks and Levelt in 1993[2]. Once you download PRET, you will be _ready_ to complete this type of analysis yourself. 12 | 13 | Requires MATLAB and the Statistics Toolbox of MATLAB. 14 | 15 | Code developed using Eyelink eyetracking data and MATLAB R2018b. 16 | 17 | ## Overview ## 18 | PRET works with data that has already been epoched and organized into separate trials. 19 | 20 | With this toolbox, you can: 21 | * Perform simple preprocessing (baseline normalization and blink interpolation) 22 | * Create models of pupil dilation for a particular task 23 | * Estimate model parameters for a given dataset and model 24 | * Bootstrap a dataset and estimate model parameters on each iteration 25 | * Plot the results of model estimation and the bootstrapping procedure 26 | * Perform estimation and/or bootstrapping procedure with multiple models for one or more datasets 27 | 28 | ## License ## 29 | PRET is a free of charge, open source toolbox distributed under the GNU General Public License version 3. 30 | 31 | ## Functions ## 32 | Function | Description 33 | ---------|------------ 34 | blinkinterp.m | performs blink interpolation as described in Mathôt 2013[3] 35 | pret_batch_process.m | performs the estimation and/or bootstrapping procedure on more than one subject 36 | pret_bootstrap.m | performs the bootstrapping procedure on one set of trials with one model 37 | pret_bootstrap_sj.m | performs the bootstrapping procedure on data in an "sj" structure with one or more models 38 | pret_calc.m | calculates the individual pupil reponse regressors and the predicted time series 39 | pret_cost.m | calculates the sum of the square errors between data and a model produced time series 40 | pret_default_options.m | establishes the default options for all PRET functions 41 | pret_estimate.m | estimates model parameters for a single pupil time series 42 | pret_estimate_sj.m | performs parameter estimation on data in an "sj" structure with one or more models 43 | pret_fake_data.m | produces artificial data by using randomly generated parameters for a specific model 44 | pret_generate_params.m | generates random parameters for a specific model 45 | pret_model.m | creates an empty "model" structure containing model specifications 46 | pret_model_check.m | checks if input "model" structure makes sense 47 | pret_optim.m | performs constrained optimization to fit model parameters to a single pupil size time series 48 | pret_plot_boots.m | plots the results of performing the bootstrapping procedure 49 | pret_plot_model.m | plots a model or the results of the estimation procedure 50 | pret_preprocess.m | performs simple preprocessing of data and/or organizes it into an "sj" structure 51 | pret_sample_script.m | a sample script demonstrating the use of PRET with sample data 52 | pupilrf.m | creates a pupil response function with the input parameters[2] 53 | 54 | ## Workflow ## 55 | Starting with data that has already been epoched and organized into separate trials, the workflow looks like this: 56 | 1. Preprocess and/or organize data into "sj" structure with pret_preprocess.m 57 | 2. Build models to test by creating "model" structures with pret_model.m and filling them out 58 | 3. Estimate parameters for each model on data in "sj" structure with pret_estimate_sj.m 59 | 4. Perform bootstrapping procedure on data in "sj" for the best model or all models using pret_bootstrap_sj.m 60 | 61 | If you have multiple subjects (datasets), you can create multiple "sj" structures and use pret_batch_process.m to perform 62 | the estimation and bootstrapping procedures for multiple models in one run. 63 | 64 | See pret_sample_script.m for a simple demonstration of this workflow. 65 | 66 | ## Considerations ## 67 | * The estimation procedure may take a signficant amount of time, depending on the number of datasets being fit 68 | * The bootstrap procedure may only be feasible with a multicore machine, depending on the number of bootstrap iterations desired and the number of datasets being fit 69 | 70 | ## References ## 71 | 1. Denison, R. N.\*, Parker, J. A.\*, and Carrasco, M. (2020). "Modeling pupil responses to rapid sequential events". Behavior Research Methods. https://doi.org/10.3758/s13428-020-01368-6 72 | \*equal contribution 73 | 2. Hoeks, B., & Levelt, W. J. M. (1993). "Pupillary dilation as a measure of attention: A quantitative system analysis". Behavior Research Methods, Instruments, & Computers, 25(1), 16–26. https://doi.org/10.3758/BF03204445 74 | 3. Mathôt, S. (2013). "A simple way to reconstruct pupil size during eye blinks". https://doi.org/10.6084/m9.figshare.688001 75 | -------------------------------------------------------------------------------- /blinkinterp.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobaparker/PRET/c2769ece8513dfbebd3067a90f11f5038b4aebf8/blinkinterp.m -------------------------------------------------------------------------------- /hanning.m: -------------------------------------------------------------------------------- 1 | function [tap] = hanning(n, str) 2 | 3 | %HANNING Hanning window. 4 | % HANNING(N) returns the N-point symmetric Hanning window in a column 5 | % vector. Note that the first and last zero-weighted window samples 6 | % are not included. 7 | % 8 | % HANNING(N,'symmetric') returns the same result as HANNING(N). 9 | % 10 | % HANNING(N,'periodic') returns the N-point periodic Hanning window, 11 | % and includes the first zero-weighted window sample. 12 | % 13 | % NOTE: Use the HANN function to get a Hanning window which has the 14 | % first and last zero-weighted samples. 15 | % 16 | % See also BARTLETT, BLACKMAN, BOXCAR, CHEBWIN, HAMMING, HANN, KAISER 17 | % and TRIANG. 18 | % 19 | % This is a drop-in replacement to bypass the signal processing toolbox 20 | 21 | % Copyright (c) 2010, Jan-Mathijs Schoffelen, DCCN Nijmegen 22 | % 23 | % This file is part of FieldTrip, see http://www.ru.nl/neuroimaging/fieldtrip 24 | % for the documentation and details. 25 | % 26 | % FieldTrip is free software: you can redistribute it and/or modify 27 | % it under the terms of the GNU General Public License as published by 28 | % the Free Software Foundation, either version 3 of the License, or 29 | % (at your option) any later version. 30 | % 31 | % FieldTrip is distributed in the hope that it will be useful, 32 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 34 | % GNU General Public License for more details. 35 | % 36 | % You should have received a copy of the GNU General Public License 37 | % along with FieldTrip. If not, see . 38 | % 39 | % $Id: hanning.m 8776 2013-11-14 09:04:48Z roboos $ 40 | 41 | if nargin==1, 42 | str = 'symmetric'; 43 | end 44 | 45 | switch str, 46 | case 'periodic' 47 | % Includes the first zero sample 48 | tap = [0; hanningX(n-1)]; 49 | case 'symmetric' 50 | % Does not include the first and last zero sample 51 | tap = hanningX(n); 52 | end 53 | 54 | function tap = hanningX(n) 55 | 56 | % compute taper 57 | N = n+1; 58 | tap = 0.5*(1-cos((2*pi*(1:n))./N))'; 59 | 60 | % make symmetric 61 | halfn = floor(n/2); 62 | tap( (n+1-halfn):n ) = flipud(tap(1:halfn)); 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /pret_batch_process.m: -------------------------------------------------------------------------------- 1 | function sjs = pret_batch_process(sjs,models,nboots,wnum,options) 2 | % pret_batch_process 3 | % sjs = pret_batch_process(sjs,models,nboots,wnum) 4 | % sjs = pret_batch_process(sjs,models,nboots,wnum,options) 5 | % 6 | % Performs the estimation procedure and/or the bootstrapping procedure to 7 | % estimate model parameters for the data for each sj structure in sjs. 8 | % 9 | % Inputs: 10 | % 11 | % sjs = structure in which each field is an sj structure output by 12 | % pret_preprocess. 13 | % 14 | % models = model structure created by pret_model and filled in by user. 15 | % Parameter values in model.ampvals, model.boxampvals, model.latvals, 16 | % model.tmaxval, model.yintval, and model.slopeval do NOT need to be 17 | % provided (unless any of those parameters are not being estimated). 18 | % *NOTE - if you want to fit multiple models, you can input an 19 | % Nx1 structure with the same fields as "model", where N is the 20 | % number of models and each element is a separate model 21 | % structure* 22 | % 23 | % nboots = if doing bootstrapping procedure, how many bootstrap 24 | % iterations to do. Can be empty if not. 25 | % 26 | % wnum = how many Matlab workers to use if doing the estimation 27 | % and/or bootstrapping procedures. 28 | % 29 | % options = options structure for pret_batch_process. Default options can be 30 | % returned by calling this function with no arguments, or see 31 | % pret_default_options. 32 | % 33 | % Output 34 | % 35 | % sjs = same structure that was input, but with the following fields 36 | % appended to each sj structure (the fields of sjs): 37 | % *if estimation done* 38 | % optim = a Nx1 structure, where N is the number of models input 39 | % in "model". Each element of this structure is an optim 40 | % structure for a single model. See pret_optim or pret_estimate for 41 | % more information about this structure. 42 | % *if bootstrapping done* 43 | % boots = a Nx1 structure, where N is the number of models input 44 | % in "model". Each element of this structure is an boots 45 | % structure for a single model. See pret_bootstrap for more 46 | % information about this structure. 47 | % 48 | % Options 49 | % 50 | % estflag (true/false) = do estimation procedure on the data in each 51 | % sj structure? Implemented by pret_estimate_sj. 52 | % 53 | % bootflag (true/false) = do bootstrapping procedure on the data in each 54 | % sj structure? Implemented by pret_boostrap_sj. 55 | % 56 | % pret_estimate_sj_options = options structure for pret_estimate_sj, 57 | % which pret_batch_process uses to perform parameter estimation for the 58 | % data in each sj structure. 59 | % 60 | % pret_bootstrap_sj_options = options structure for pret_bootstrap_sj, 61 | % which pret_batch_process uses to perform the bootstrapping procedure for the 62 | % data in each sj structure. 63 | % 64 | % pret_model_check = options for pret_model_check 65 | % 66 | % 67 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 68 | % 69 | % This program is free software: you can redistribute it and/or modify 70 | % it under the terms of the GNU General Public License as published by 71 | % the Free Software Foundation, either version 3 of the License, or 72 | % (at your option) any later version. 73 | % 74 | % This program is distributed in the hope that it will be useful, 75 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 76 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 77 | % GNU General Public License for more details. 78 | % 79 | % You should have received a copy of the GNU General Public License 80 | % along with this program. If not, see . 81 | % 82 | 83 | if nargin < 5 84 | opts = pret_default_options(); 85 | options = opts.pret_batch_process; 86 | clear opts 87 | if nargin < 1 88 | sjs = options; 89 | return 90 | end 91 | end 92 | 93 | %OPTIONS 94 | estflag = options.estflag; 95 | bootflag = options.bootflag; 96 | pret_estimate_sj_options = options.pret_estimate_sj; 97 | pret_bootstrap_sj_options = options.pret_bootstrap_sj; 98 | pret_model_check_options = options.pret_model_check; 99 | 100 | check models 101 | for mm = 1:length(model) 102 | try 103 | pret_model_check(model(mm),pret_model_check_options) 104 | catch 105 | fprintf('\nError in model %d\n',mm) 106 | pret_model_check(model(mm),pret_model_check_options) 107 | end 108 | end 109 | 110 | sjfields = fieldnames(sjs); 111 | 112 | for s = 1:length(sjfields) 113 | fprintf('Subject %s\n',sjfields{s}) 114 | if estflag 115 | sjs.(sjfields{s}) = pret_estimate_sj(sjs.(sjfields{s}),models,wnum,pret_estimate_sj_options); 116 | end 117 | if bootflag 118 | sjs.(sjfields{s}) = pret_bootstrap_sj(sjs.(sjfields{s}),models,nboots,wnum,pret_bootstrap_sj_options); 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /pret_bootstrap.m: -------------------------------------------------------------------------------- 1 | function [boots, bootestims] = pret_bootstrap(data,samplerate,trialwindow,model,nboots,wnum,options) 2 | % pret_bootstrap 3 | % boots = pret_bootstrap(data,samplerate,trialwindow,model,nboots,wnum) 4 | % boots = pret_bootstrap(data,samplerate,trialwindow,model,nboots,wnum,options) 5 | % [boots, bootestims] = pret_bootstrap(data,samplerate,trialwindow,model,nboots,wnum,options) 6 | % options = pret_bootstrap() 7 | % 8 | % Bootstrapping procedure for estimating model parameters. Calculates a set 9 | % of bootstrapped means from the data provided, then performs the 10 | % optimization algorithm on each bootstrapped mean. 11 | % 12 | % Inputs: 13 | % 14 | % data = a 2D matrix containing all of the trials for one mean, in 15 | % the form of trial by time. 16 | % 17 | % samplerate = sampling rate of data in Hz. 18 | % 19 | % trialwindow = a 2 element vector containing the starting and ending 20 | % times (in ms) of the trial epoch. 21 | % 22 | % model = model structure created by pret_model and filled in by user. 23 | % Parameter values in model.ampvals, model.boxampvals, model.latvals, 24 | % model.tmaxval, model.yintval, and model.slopeval do NOT need to be 25 | % provided (unless any of those parameters are not being estimated). 26 | % 27 | % nboots = number of bootstrap iterations to perform. 28 | % 29 | % wnum = number of workers used by matlab's parallel pool to complete 30 | % the process (parpool will not be initialized if set to 1). 31 | % 32 | % options = options structure for pret_bootstrap. Default options can be 33 | % returned by calling this function with no arguments, or see 34 | % pret_default_options. 35 | % 36 | % Outputs: 37 | % 38 | % boots = a structure containing the parameters estimated for each 39 | % bootstrap iteration. It contains the following fields: 40 | % eventtimes = a copy of eventtimes from "model". 41 | % boxtimes = a copy of boxtimes from "model". 42 | % samplerate = a copy of samplerate from "model". 43 | % window = a copy of window from "model". 44 | % 45 | % ampvals = the estimated event amplitude values for each 46 | % bootstrap iteration, where the fist dimension is bootstrap 47 | % iteration and the second dimension is event. 48 | % boxampvals = the estimated box regressor amplitude values. 49 | % latvals = the estimated event latency values. 50 | % tmaxvals = the estimated tmax values. 51 | % yintvals = the estimated y-intercept values. 52 | % slopevals = the estimated slope values 53 | % 54 | % cost = the sum of square errors between the optimized 55 | % parameters and the actual data. 56 | % R2 = the R^2 goodness of fit value. 57 | % BICrel = the relative BIC value of the model fit 58 | % *relative because we use the guassian simplfictation of the 59 | % BIC 60 | % *since it is relative, only use to compare models/fits on 61 | % data from the same task 62 | % 63 | % ampmedians, boxampmedians, latmedians, tmaxmedian, yintmedian, slopemedian = 64 | % the medians of ampvals, boxampvals, latvals, tmaxvals, yintvals, 65 | % and slopevals respectively. 66 | % 67 | % amp95CIs, boxamp95CIs, lat95CIs, tmax95CI, yint95CI, slope95CI = 68 | % the 95 confidence intervals of ampvals, boxampvals, latvals, 69 | % tmaxvals, yintvals, and slopevals respectively. 70 | % 71 | % bootestims = an optional output option. A structure with a length 72 | % of nboots, where each element is an estim structure output by 73 | % running pret_estimate for single bootstrap iteration. 74 | % *Note - each estim structure can be input into pret_plot_model, pret_calc, 75 | % or pret_cost in the place of the "model" input* 76 | % 77 | % Options 78 | % 79 | % bootplotflag (true/false) = plot summary figures with the 80 | % distribution of each parameter's bootstrap estimations. 81 | % 82 | % pret_estimate_options = options structure for pret_estimate, 83 | % which pret_bootstrap uses to perform each bootstrap iteration. 84 | % 85 | % pret_model_check = options for pret_model_check 86 | % 87 | % 88 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 89 | % 90 | % This program is free software: you can redistribute it and/or modify 91 | % it under the terms of the GNU General Public License as published by 92 | % the Free Software Foundation, either version 3 of the License, or 93 | % (at your option) any later version. 94 | % 95 | % This program is distributed in the hope that it will be useful, 96 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 97 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 98 | % GNU General Public License for more details. 99 | % 100 | % You should have received a copy of the GNU General Public License 101 | % along with this program. If not, see . 102 | % 103 | 104 | if nargin < 7 105 | opts = pret_default_options(); 106 | options = opts.pret_bootstrap; 107 | clear opts 108 | if nargin < 1 109 | boots = options; 110 | return 111 | end 112 | end 113 | 114 | %OPTIONS 115 | bootplotflag = options.bootplotflag; 116 | pret_estimate_options = options.pret_estimate; 117 | pret_model_check_options = options.pret_model_check; 118 | 119 | sfact = samplerate/1000; 120 | time = trialwindow(1):1/sfact:trialwindow(2); 121 | 122 | %check inputs 123 | %simple check of input model structure 124 | pret_model_check(model,pret_model_check_options) 125 | 126 | %samplerate, trialwindow vs data 127 | if length(time) ~= size(data,2) 128 | error('The number of time points according to samplerate and trialwindow does not equal the number of data points (2nd dimension of data) in a trial') 129 | end 130 | 131 | %sample rate vs model sample rate 132 | if samplerate ~= model.samplerate 133 | error('The input sample rate and the sample rate in model do not match') 134 | end 135 | 136 | %model time window vs time points 137 | if ~(any(model.window(1) == time)) || ~(any(model.window(2) == time )) 138 | error('Model time window does not fall on time points according to sample rate and trial window') 139 | end 140 | 141 | %crop data to match model.window 142 | datalb = find(model.window(1) == time); 143 | dataub = find(model.window(2) == time); 144 | data = data(:,datalb:dataub); 145 | 146 | %create bootstrap means of trials in data 147 | rng(0) 148 | means = bootstrp(nboots,@nanmean,data); 149 | 150 | bootestims = struct('eventtimes',model.eventtimes,'boxtimes',model.boxtimes,'samplerate',model.samplerate,'window',model.window,'ampvals',[],'boxampvals',[],'latvals',[],'tmaxval',[],'yintval',[],'slopeval',[],'numparams',[],'cost',[],'R2',[],'BICrel',[]); 151 | modelsamplerate = model.samplerate; 152 | modelwindow = model.window; 153 | 154 | %estimate model parameters for each bootstrap mean 155 | fprintf('\nBeginning bootstrapping, %d iterations to be completed\n',nboots) 156 | if wnum == 1 157 | for nb = 1:nboots 158 | fprintf('\nStart iteration %d\n',nb) 159 | bootestims(nb) = pret_estimate(means(nb,:),modelsamplerate,modelwindow,model,1,pret_estimate_options); 160 | fprintf('\nEnd iteration %d\n',nb) 161 | end 162 | else 163 | p = gcp('nocreate'); 164 | if isempty(p) 165 | parpool(wnum); 166 | end 167 | parfor nb = 1:nboots 168 | fprintf('\nStart iteration %d\n',nb) 169 | bootestims(nb) = pret_estimate(means(nb,:),modelsamplerate,modelwindow,model,1,pret_estimate_options); 170 | fprintf('\nEnd iteration %d\n',nb) 171 | end 172 | end 173 | fprintf('Boostrapping completed!\n') 174 | 175 | boots = struct('eventtimes',model.eventtimes,'boxtimes',{model.boxtimes},'samplerate',model.samplerate,'window',model.window,'ampvals',nan(nboots,length(model.eventtimes)),'boxampvals',nan(nboots,length(model.boxtimes)),'latvals',nan(nboots,length(model.eventtimes)),'tmaxvals',nan(nboots,1),'yintvals',nan(nboots,1),'slopevals',nan(nboots,1),'costs',nan(nboots,1),'R2',nan(nboots,1)); 176 | 177 | for nb = 1:nboots 178 | boots.ampvals(nb,:) = bootestims(nb).ampvals; 179 | boots.boxampvals(nb,:) = bootestims(nb).boxampvals; 180 | boots.latvals(nb,:) = bootestims(nb).latvals; 181 | boots.tmaxvals(nb,:) = bootestims(nb).tmaxval; 182 | boots.yintvals(nb,:) = bootestims(nb).yintval; 183 | boots.slopevals(nb,:) = bootestims(nb).slopeval; 184 | boots.costs(nb) = bootestims(nb).cost; 185 | boots.R2(nb) = bootestims(nb).R2; 186 | end 187 | 188 | boots.ampmedians = nanmedian(boots.ampvals,1); 189 | boots.boxampmedians = nanmedian(boots.boxampvals,1); 190 | boots.latmedians = nanmedian(boots.latvals,1); 191 | boots.tmaxmedian = nanmedian(boots.tmaxvals,1); 192 | boots.yintmedian = nanmedian(boots.yintvals,1); 193 | boots.slopemedian = nanmedian(boots.slopevals,1); 194 | 195 | boots.amp95CIs = [prctile(boots.ampvals,2.5,1) ; prctile(boots.ampvals,97.5,1)]; 196 | boots.boxamp95CIs = [prctile(boots.boxampvals,2.5,1) ; prctile(boots.boxampvals,97.5,1)]; 197 | boots.lat95CIs = [prctile(boots.latvals,2.5,1) ; prctile(boots.latvals,97.5,1)]; 198 | boots.tmax95CI = [prctile(boots.tmaxvals,2.5) ; prctile(boots.tmaxvals,97.5)]; 199 | boots.yint95CI = [prctile(boots.yintvals,2.5) ; prctile(boots.yintvals,97.5)]; 200 | boots.slope95CI = [prctile(boots.slopevals,2.5) ; prctile(boots.slopevals,97.5)]; 201 | 202 | if bootplotflag 203 | pret_plot_boots(boots,model); 204 | end 205 | -------------------------------------------------------------------------------- /pret_bootstrap_sj.m: -------------------------------------------------------------------------------- 1 | function sj = pret_bootstrap_sj(sj,model,nboots,wnum,options) 2 | % pret_bootstrap_sj 3 | % sj = pret_bootstrap_sj(sj,model,nboots,wnum) 4 | % sj = pret_bootstrap_sj(sj,model,nboots,wnum,options) 5 | % options = pret_bootstrap_sj() 6 | % 7 | % Performs the bootstrapping procedure for estimating model parameters on 8 | % each set of data that has been preprocessed/organized into a sj structure 9 | % by pret_preprocess. 10 | % 11 | % Inputs: 12 | % 13 | % sj = structure output by pret_preprocess containing data in a format 14 | % that pret_estimate_sj uses. 15 | % 16 | % model = model structure created by pret_model and filled in by user. 17 | % Parameter values in model.ampvals, model.boxampvals, model.latvals, 18 | % model.tmaxval, model.yintval, and model.slopeval need to be 19 | % provided only if they are not being estimated. 20 | % *NOTE - if you want to fit multiple models, you can input an 21 | % Nx1 structure with the same fields as "model", where N is the 22 | % number of models and each element is a separate model 23 | % structure* 24 | % 25 | % wnum = number of workers used by matlab's parallel pool to complete 26 | % the process (parpool will not be initialized if set to 1). 27 | % 28 | % options = options structure for pret_bootstrap_sj. Default options can be 29 | % returned by calling this function with no arguments, or see 30 | % pret_default_options. 31 | % 32 | % Output 33 | % 34 | % sj = same structure that was input, but with the additional field 35 | % appended: 36 | % boots = a Nx1 structure, where N is the number of models input 37 | % in "model". Each element of this structure is an boots 38 | % structure for a single model. See pret_bootstrap for more 39 | % information about this structure. 40 | % 41 | % Options 42 | % 43 | % pret_bootstrap_options = options structure for pret_bootstrap, 44 | % which pret_bootstrap_sj uses to perform parameter estimation for each 45 | % set of data in sj. 46 | % 47 | % saveflag (true/false) = save a .mat file with the output sj 48 | % variable? 49 | % 50 | % savefile = if saveflag true, save .mat file to this dir (include 51 | % name of course) 52 | % 53 | % pret_model_check = options for pret_model_check 54 | % 55 | % 56 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 57 | % 58 | % This program is free software: you can redistribute it and/or modify 59 | % it under the terms of the GNU General Public License as published by 60 | % the Free Software Foundation, either version 3 of the License, or 61 | % (at your option) any later version. 62 | % 63 | % This program is distributed in the hope that it will be useful, 64 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 65 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 66 | % GNU General Public License for more details. 67 | % 68 | % You should have received a copy of the GNU General Public License 69 | % along with this program. If not, see . 70 | % 71 | 72 | if nargin < 5 73 | opts = pret_default_options(); 74 | options = opts.pret_bootstrap_sj; 75 | clear opts 76 | if nargin < 1 77 | sj = options; 78 | return 79 | end 80 | end 81 | 82 | %OPTIONS 83 | pret_bootstrap_options = options.pret_bootstrap; 84 | saveflag = options.saveflag; 85 | savefile = options.savefile; 86 | pret_model_check_options = options.pret_model_check; 87 | 88 | %check models 89 | for mm = 1:length(model) 90 | try 91 | pret_model_check(model(mm),pret_model_check_options) 92 | catch 93 | fprintf('\nError in model %d\n',mm) 94 | pret_model_check(model(mm),pret_model_check_options) 95 | end 96 | end 97 | 98 | sj.boots = []; 99 | 100 | for mm = 1:length(model) 101 | fprintf('\nModel %d',mm) 102 | for cc = 1:length(sj.conditions) 103 | fprintf('\nCondition %s\n',sj.conditions{cc}) 104 | sj.boots(mm).(sj.conditions{cc}) = pret_bootstrap(sj.(sj.conditions{cc}),sj.samplerate,sj.trialwindow,model(mm),nboots,wnum,pret_bootstrap_options); 105 | end 106 | if saveflag 107 | save(savefile,'sj') 108 | end 109 | end -------------------------------------------------------------------------------- /pret_calc.m: -------------------------------------------------------------------------------- 1 | function [Ycalc, X] = pret_calc(model,options) 2 | % pret_calc 3 | % [Ycalc, X] = pret_calc(model) 4 | % [Ycalc, X] = pret_calc(model,options) 5 | % [Ycalc, X] = pret_calc(optim,options) 6 | % options = pret_calc() 7 | % 8 | % Calculates the time series and its constituent regresssors resulting from the 9 | % model parameters and specifications in the given model structure. 10 | % 11 | % Inputs: 12 | % 13 | % model = model structure created by pret_model and filled in by user. 14 | % Parameter values in model.ampvals, model.boxampvals, model.latvals, 15 | % model.tmaxval, model.yintval, and model.slopeval must be provided. 16 | % *Note - an optim/estim structure from pret_estimate, pret_bootstrap, or 17 | % pret_optim can be input in the place of model* 18 | % 19 | % options = options structure for pret_calc. Default options can be 20 | % returned by calling this function with no arguments. 21 | % 22 | % Outputs: 23 | % 24 | % Ycalc = time series created using the parameters and specifications 25 | % provided in the model input. 26 | % 27 | % X = 2D matrix of regressors that are summed together and with the 28 | % y-intercept parameter to create Ycalc. 1st dimension is regressor 29 | % and 2nd dimension is time. 30 | % 31 | % Options 32 | % 33 | % n = parameter used to generate pupil response function. Canonical 34 | % value of 10.1 is the default. See the function "pupilrf" and 35 | % Hoeks&Levelt 1993 for more information. 36 | % 37 | % pret_model_check = options for pret_model_check 38 | % 39 | % 40 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 41 | % 42 | % This program is free software: you can redistribute it and/or modify 43 | % it under the terms of the GNU General Public License as published by 44 | % the Free Software Foundation, either version 3 of the License, or 45 | % (at your option) any later version. 46 | % 47 | % This program is distributed in the hope that it will be useful, 48 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 49 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 50 | % GNU General Public License for more details. 51 | % 52 | % You should have received a copy of the GNU General Public License 53 | % along with this program. If not, see . 54 | % 55 | 56 | if nargin < 2 57 | opts = pret_default_options(); 58 | options = opts.pret_calc; 59 | clear opts 60 | if nargin < 1 61 | Ycalc = options; 62 | return 63 | end 64 | end 65 | 66 | %OPTIONS 67 | n = options.n; 68 | pret_model_check_options = options.pret_model_check; 69 | 70 | %check inputs 71 | if ~isfield(model,'ampflag') 72 | fprintf('Treating input "model" as an optim/estim structure\n') 73 | optim_check(model) 74 | else 75 | pret_model_check(model,pret_model_check_options) 76 | end 77 | 78 | sfact = model.samplerate/1000; 79 | time = model.window(1):1/sfact:model.window(2); 80 | 81 | X1 = nan(length(model.eventtimes),length(time)); 82 | X2 = nan(length(model.boxtimes),length(time)); 83 | 84 | for xx = 1:size(X1,1) 85 | 86 | h = pupilrf(time,n,model.tmaxval,model.eventtimes(xx)+model.latvals(xx)); 87 | temp = conv(h,model.ampvals(xx)); 88 | X1(xx,:) = temp; 89 | 90 | end 91 | 92 | for bx = 1:size(X2,1) 93 | 94 | h = pupilrf(time,n,model.tmaxval,model.boxtimes{bx}(1)); 95 | temp = conv(h,(ones(1,(model.boxtimes{bx}(2)-model.boxtimes{bx}(1))*sfact+1))); 96 | temp = (temp/max(temp)) .* model.boxampvals(bx); 97 | X2(bx,:) = temp(1:length(time)); 98 | 99 | end 100 | 101 | X = [X1 ; X2]; 102 | Ycalc = sum(X,1) + model.slopeval*time + model.yintval; 103 | 104 | function optim_check(model) 105 | %window 106 | if length(model.window) ~= 2 107 | error('model.window must be a two element vector') 108 | end 109 | 110 | %samplerate 111 | if length(model.samplerate) ~= 1 112 | error('model.samplerate must be provided as a single value') 113 | end 114 | 115 | sfact = model.samplerate/1000; 116 | time = model.window(1):1/sfact:model.window(2); 117 | if ~(any(model.window(1) == time)) || ~(any(model.window(2) == time)) 118 | error('"model.window" not compatible with "model.samplerate"') 119 | end 120 | 121 | %event amplitude 122 | if length(model.eventtimes) ~= length(model.ampvals) 123 | error('Number of defualt event amplitudes not equal to number of events') 124 | end 125 | 126 | %box amplitude 127 | if ~isempty(model.boxtimes) 128 | for ii = 1:length(model.boxtimes) 129 | if length(model.boxtimes{ii}) ~= 2 130 | error('All cells in boxtimes must be a 2 element vector') 131 | end 132 | end 133 | end 134 | if length(model.boxtimes) ~= length(model.boxampvals) 135 | error('Number of defualt box amplitudes not equal to number of boxes') 136 | end 137 | 138 | %latency 139 | if length(model.eventtimes) ~= length(model.latvals) 140 | error('Number of defualt event latency not equal to number of events') 141 | end 142 | 143 | %tmax 144 | if length(model.tmaxval) ~= 1 145 | error('Number of default tmax values not equal to 1') 146 | end 147 | 148 | %y-intercept 149 | if length(model.yintval) ~= 1 150 | error('Number of default y-intercept values not equal to 1') 151 | end 152 | 153 | %slope 154 | if length(model.slopeval) ~= 1 155 | error('Number of default slope values not equal to 1') 156 | end 157 | end 158 | 159 | end 160 | 161 | -------------------------------------------------------------------------------- /pret_cost.m: -------------------------------------------------------------------------------- 1 | function cost = pret_cost(data,samplerate,trialwindow,model,options) 2 | % pret_cost 3 | % cost = pret_cost(data,samplerate,trialwindow,model) 4 | % cost = pret_cost(data,samplerate,trialwindow,model,options) 5 | % options = pret_cost() 6 | % 7 | % Calculates the sum of the square errors between some input pupil size 8 | % time series "data" and a time series created from the specifications and 9 | % parameters in "model". If a MxN matrix with M time series is input into 10 | % "data", the cost will be computed between each time series and the single 11 | % time series produced from "model" and summed. 12 | % 13 | % Inputs: 14 | % 15 | % data = a single pupil size time series as a row vector OR a MxN 16 | % matrix with M time series. 17 | % 18 | % samplerate = sampling rate of data in Hz. 19 | % 20 | % trialwindow = a 2 element vector containing the starting and ending 21 | % times (in ms) of the trial epoch. 22 | % 23 | % model = model structure created by pret_model and filled in by user. 24 | % Parameter values in model.ampvals, model.boxampvals, model.latvals, 25 | % model.tmaxval, and model.yintval must be provided. 26 | % *Note - an optim structure from pret_estimate, pret_bootstrap, or 27 | % pret_optim can be input in the place of model* 28 | % 29 | % options = options structure for pret_cost. Default options can be 30 | % returned by calling this function with no arguments. 31 | % 32 | % Outputs: 33 | % 34 | % cost = sum of the square errors between "data" and time series 35 | % created using "model" 36 | % 37 | % Options 38 | % 39 | % pret_calc_options = options structure for pret_calc, which pret_cost uses 40 | % to produce the time series from "model". 41 | % 42 | % 43 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 44 | % 45 | % This program is free software: you can redistribute it and/or modify 46 | % it under the terms of the GNU General Public License as published by 47 | % the Free Software Foundation, either version 3 of the License, or 48 | % (at your option) any later version. 49 | % 50 | % This program is distributed in the hope that it will be useful, 51 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 52 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 53 | % GNU General Public License for more details. 54 | % 55 | % You should have received a copy of the GNU General Public License 56 | % along with this program. If not, see . 57 | % 58 | 59 | if nargin < 5 60 | opts = pret_default_options(); 61 | options = opts.pret_cost; 62 | clear opts 63 | if nargin < 1 64 | cost = options; 65 | return 66 | end 67 | end 68 | 69 | %OPTIONS 70 | pret_calc_options = options.pret_calc; 71 | 72 | sfact = samplerate/1000; 73 | time = trialwindow(1):1/sfact:trialwindow(2); 74 | 75 | %check inputs 76 | pret_model_check(model) 77 | 78 | %samplerate, trialwindow vs data 79 | if length(time) ~= length(data) 80 | error('The number of time points does not equal the number of data points') 81 | end 82 | 83 | %sample rate vs model sample rate 84 | if samplerate ~= model.samplerate 85 | error('The input sample rate and the sample rate in model do not match') 86 | end 87 | 88 | %model time window vs time points 89 | if ~(any(model.window(1) == time)) || ~(any(model.window(2) == time )) 90 | error('Model time window does not fall on time points according to sample rate and trial window') 91 | end 92 | 93 | %how many time series to fit simultaneously? 94 | nts = size(data,1); 95 | 96 | %crop data to match model.window 97 | datalb = find(model.window(1) == time); 98 | dataub = find(model.window(2) == time); 99 | data = data(:,datalb:dataub); 100 | 101 | Ycalc = pret_calc(model,pret_calc_options); 102 | 103 | if nts>1 104 | % concatenate time series 105 | temp = data'; 106 | data = temp(:)'; 107 | 108 | % concatenate model prediction 109 | Ycalc = repmat(Ycalc,1,nts); 110 | end 111 | 112 | cost = nansum((data-Ycalc).^2); 113 | 114 | -------------------------------------------------------------------------------- /pret_default_options.m: -------------------------------------------------------------------------------- 1 | function options = pret_default_options() 2 | % pret_default_options 3 | % options = pret_default_options 4 | % 5 | % Returns the structure "options", which contains the default options for 6 | % every function inluded in this package in fields titled as the function 7 | % name. The fields of this output structure can be input as the last 8 | % argument into many functions to specify options. For example, 9 | % "options.pret_estimate" can be entered as the last argument into the 10 | % pret_estimate function. You can also obtain function specific option 11 | % structures by calling the function with no input arguments. 12 | % 13 | % Default options for most functions can be changed by modifying this 14 | % function directly. 15 | % 16 | % 17 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 18 | % 19 | % This program is free software: you can redistribute it and/or modify 20 | % it under the terms of the GNU General Public License as published by 21 | % the Free Software Foundation, either version 3 of the License, or 22 | % (at your option) any later version. 23 | % 24 | % This program is distributed in the hope that it will be useful, 25 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 | % GNU General Public License for more details. 28 | % 29 | % You should have received a copy of the GNU General Public License 30 | % along with this program. If not, see . 31 | % 32 | 33 | options = struct('pret_model_check',[],'pret_calc',[],'pret_cost',[],'pret_optim',[],'pret_estimate',[],'pret_bootstrap',[],... 34 | 'pret_generate_params',[],'pret_fake_data',[],'pret_preprocess',[],'pret_estimate_sj',[],... 35 | 'pret_bootstrap_sj',[],'pret_batch_process',[],'pret_plot_boots',[]); 36 | 37 | %% pret_model_check 38 | options.pret_model_check.checkparams = false; 39 | 40 | %% pret_calc 41 | options.pret_calc.n = 10.1; 42 | options.pret_calc.pret_model_check = options.pret_model_check; 43 | options.pret_calc.pret_model_check.checkparams = true; 44 | 45 | %% pret_cost 46 | options.pret_cost.pret_calc = options.pret_calc; 47 | options.pret_cost.pret_model_check = options.pret_model_check; 48 | options.pret_cost.pret_model_check.checkparams = true; 49 | 50 | %% pret_optim 51 | options.pret_optim.optimplotflag = true; 52 | options.pret_optim.ampfact = 1/10; 53 | options.pret_optim.boxampfact = 1/10; 54 | options.pret_optim.latfact = 1/1000; 55 | options.pret_optim.tmaxfact = 1/1000; 56 | options.pret_optim.yintfact = 1/10; 57 | options.pret_optim.slopefact = 100; 58 | options.pret_optim.pret_cost = options.pret_cost; 59 | options.pret_optim.pret_model_check = options.pret_model_check; 60 | options.pret_optim.pret_model_check.checkparams = true; 61 | 62 | %input values/options for fmincon 63 | options.pret_optim.fmincon = []; 64 | options.pret_optim.fmincon.A = []; 65 | options.pret_optim.fmincon.B = []; 66 | options.pret_optim.fmincon.Aeq = []; 67 | options.pret_optim.fmincon.Beq = []; 68 | options.pret_optim.fmincon.NONLCON = []; 69 | options.pret_optim.fmincon.options = fmincon('defaults'); 70 | options.pret_optim.fmincon.options.Display = 'off'; 71 | 72 | %% pret_generate_params 73 | options.pret_generate_params.nbins = 50; 74 | options.pret_generate_params.sigma = 0.05; 75 | options.pret_generate_params.ampfact = 1/10; 76 | options.pret_generate_params.boxampfact = 1/10; 77 | options.pret_generate_params.latfact = 1/1000; 78 | options.pret_generate_params.tmaxfact = 1/1000; 79 | options.pret_generate_params.yintfact = 1/10; 80 | options.pret_generate_params.slopefact = 100; 81 | options.pret_generate_params.pret_model_check = options.pret_model_check; 82 | 83 | %% pret_estimate 84 | options.pret_estimate.searchnum = 2000; 85 | options.pret_estimate.optimnum = 40; 86 | options.pret_estimate.parammode = 'uniform'; 87 | options.pret_estimate.pret_generate_params = options.pret_generate_params; 88 | options.pret_estimate.pret_optim = options.pret_optim; 89 | options.pret_estimate.pret_model_check = options.pret_model_check; 90 | 91 | %% pret_bootstrap 92 | options.pret_bootstrap.bootplotflag = true; 93 | options.pret_bootstrap.pret_estimate = options.pret_estimate; 94 | options.pret_bootstrap.pret_model_check = options.pret_model_check; 95 | 96 | %% pret_plot_model 97 | options.pret_plot_model.pret_calc = options.pret_calc; 98 | 99 | %% pret_fake_data 100 | options.pret_fake_data.pret_generate_params = options.pret_generate_params; 101 | options.pret_fake_data.pret_model_check = options.pret_model_check; 102 | 103 | %% pret_preprocess 104 | options.pret_preprocess.normflag = true; 105 | options.pret_preprocess.blinkflag = true; 106 | 107 | %blinkinterp arguments 108 | options.pret_preprocess.th1 = 5; 109 | options.pret_preprocess.th2 = 3; 110 | options.pret_preprocess.bwindow = 50; 111 | options.pret_preprocess.betblink = 75; 112 | 113 | %% pret_estimate_sj 114 | options.pret_estimate_sj.pret_estimate = options.pret_estimate; 115 | options.pret_estimate_sj.trialmode = 'mean'; 116 | options.pret_estimate_sj.saveflag = false; 117 | options.pret_estimate_sj.savefile = ''; 118 | options.pret_estimate_sj.pret_model_check = options.pret_model_check; 119 | 120 | %% pret_bootstrap_sj 121 | options.pret_bootstrap_sj.pret_bootstrap = options.pret_bootstrap; 122 | options.pret_bootstrap_sj.saveflag = false; 123 | options.pret_bootstrap_sj.savefile = ''; 124 | options.pret_bootstrap_sj.pret_model_check = options.pret_model_check; 125 | 126 | %% pret_batch_process 127 | options.pret_batch_process.estflag = true; 128 | options.pret_batch_process.pret_estimate_sj = options.pret_estimate_sj; 129 | options.pret_batch_process.bootflag = true; 130 | options.pret_batch_process.pret_bootstrap_sj = options.pret_bootstrap_sj; 131 | options.pret_batch_process.pret_model_check = options.pret_model_check; 132 | 133 | %% pret_plot_boots 134 | options.pret_plot_boots.pret_model_check = options.pret_model_check; 135 | options.pret_plot_boots.pret_model_check.checkparams = false; 136 | -------------------------------------------------------------------------------- /pret_estimate.m: -------------------------------------------------------------------------------- 1 | function [estim, searchoptims, searchpoints] = pret_estimate(data,samplerate,trialwindow,model,wnum,options) 2 | % pret_estimate 3 | % [estim, searchoptims, searchpoints] = pret_estimate(data,samplerate,trialwindow,model,wnum) 4 | % [estim, searchoptims, searchpoints] = pret_estimate(data,samplerate,trialwindow,model,wnum,options) 5 | % options = pret_estimate() 6 | % 7 | % Optimization algorithm for estimating the model parameters that result in 8 | % the best fit to the data. First, the cost function is evaluated for a 9 | % large number (default 2000) of points sampled from across 10 | % parameter space. Then, the subset (default 40) that evaluate to the lowest value 11 | % of the cost function are used as starting points for fmincon. The output 12 | % set of parameters that result in the lowest cost is taken as the best 13 | % estimate. 14 | % 15 | % Inputs: 16 | % 17 | % data = a single pupil size time series as a row vector OR an MxN 18 | % matrix with M time series. If a matrix, will compute single set of 19 | % parameters minimizing the cost for all time series. 20 | % 21 | % samplerate = sampling rate of data in Hz. 22 | % 23 | % trialwindow = a 2 element vector containing the starting and ending 24 | % times (in ms) of the trial epoch. Will NOT be the window the model 25 | % is estimated on (that is in model.window). 26 | % 27 | % model = model structure created by pret_model and filled in by user. 28 | % Parameter values in model.ampvals, model.boxampvals, model.latvals, 29 | % model.tmaxval, model.yintval, and model.slopeval do not need to be 30 | % provided if they are being estimated but should be provided if they 31 | % are not. 32 | % 33 | % wnum = number of workers used by matlab's parallel pool to complete 34 | % the process (parpool will not be initialized if set to 1). 35 | % 36 | % options = options structure for pret_estimate. Default options can be 37 | % returned by calling this function with no arguments, or see 38 | % pret_default_options. 39 | % 40 | % Outputs: 41 | % 42 | % estim = a structure containing the parameters estimated by fmincon 43 | % with the following fileds: 44 | % eventtimes = a copy of eventtimes from "model". 45 | % boxtimes = a copy of boxtimes from "model". 46 | % samplerate = a copy of samplerate from "model". 47 | % window a copy of window from "model". 48 | % ampvals = the estimated event amplitude values. 49 | % boxampvals = the estimated box regressor amplitude values. 50 | % latvals = the estimated event latency values. 51 | % tmaxval = the estimated tmax value. 52 | % yintval = the estimated y-intercept value. 53 | % slopeval = the estimated slope value. 54 | % numparams = number of parameters fit. 55 | % cost = the sum of square errors between the optimized 56 | % parameters and the actual data. 57 | % R2 = the R^2 goodness of fit value 58 | % BICrel = the relative BIC value of the model fit 59 | % *relative because we use the guassian simplfictation of the 60 | % BIC 61 | % *since it is relative, only use to compare models/fits on 62 | % data from the same task 63 | % 64 | % *Note - can be input into pret_plot_model, pret_calc, or pret_cost in the 65 | % place of the "model" input* 66 | % 67 | % searchoptims = an optional output structure containing the 68 | % parameters estimated by fmincon for all optimization runs. 69 | % It has the same fields as optim, but has a length equal to 70 | % options.optimnum. 71 | % 72 | % Options 73 | % 74 | % options.searchnum (2000) = the number of points sampled from parameter 75 | % space where the cost function is evaluated. 76 | % 77 | % options.optimnum (40) = the number of optimizations to be run, using the 78 | % optimnum number of parameter points with the lowest costs. 79 | % 80 | % options.parammode ('uniform') = the mode used to generate the 81 | % parameters for the search of parameter space. See 82 | % pret_generate_params for more information. 83 | % 84 | % pret_generate_params_options = options structure for pret_generate_params, 85 | % which pret_estimate uses to generate the parameters for the search of 86 | % parameter space. 87 | % 88 | % pret_optim_options = options structure for pret_optim, 89 | % which pret_estimate uses to perform each individual optimization. 90 | % 91 | % pret_cost_options = options structure for pret_cost, which pret_estimate 92 | % uses to evaluate the cost function at each point sampled from 93 | % parameter space. 94 | % 95 | % pret_model_check = options for pret_model_check 96 | % 97 | % 98 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 99 | % 100 | % This program is free software: you can redistribute it and/or modify 101 | % it under the terms of the GNU General Public License as published by 102 | % the Free Software Foundation, either version 3 of the License, or 103 | % (at your option) any later version. 104 | % 105 | % This program is distributed in the hope that it will be useful, 106 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 107 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 108 | % GNU General Public License for more details. 109 | % 110 | % You should have received a copy of the GNU General Public License 111 | % along with this program. If not, see . 112 | % 113 | 114 | if nargin < 6 115 | opts = pret_default_options(); 116 | options = opts.pret_estimate; 117 | clear opts 118 | if nargin < 1 119 | estim = options; 120 | return 121 | end 122 | end 123 | 124 | %OPTIONS 125 | pret_generate_params_options = options.pret_generate_params; 126 | pret_optim_options = options.pret_optim; 127 | pret_cost_options = options.pret_optim.pret_cost; 128 | searchnum = options.searchnum; 129 | optimnum = options.optimnum; 130 | parammode = options.parammode; 131 | pret_model_check_options = options.pret_model_check; 132 | 133 | sfact = samplerate/1000; 134 | time = trialwindow(1):1/sfact:trialwindow(2); 135 | 136 | %check inputs 137 | %simple check of input model structure 138 | pret_model_check(model,pret_model_check_options) 139 | 140 | %data is a vector 141 | % if size(data,1) ~= 1 142 | % error('The "data" argument must be a row vector') 143 | % end 144 | 145 | %samplerate, trialwindow vs data 146 | if length(time) ~= size(data,2) 147 | error('The number of time points according to samplerate and trialwindow does not equal the number of data points in data') 148 | end 149 | 150 | %sample rate vs model sample rate 151 | if samplerate ~= model.samplerate 152 | error('The input sample rate and the sample rate in model do not match') 153 | end 154 | 155 | %model time window vs time points 156 | if ~(any(model.window(1) == time)) || ~(any(model.window(2) == time )) 157 | error('Model time window does not fall on time points according to sample rate and trial window') 158 | end 159 | 160 | %generate starting parameters for coarse search in parameter space 161 | searchpoints = pret_generate_params(searchnum,parammode,model,pret_generate_params_options); 162 | 163 | %crop data to match model.window 164 | datalb = find(model.window(1) == time); 165 | dataub = find(model.window(2) == time); 166 | data = data(:,datalb:dataub); 167 | 168 | %evaluate cost function with parameter sets distributed across the parameter 169 | %space to determine which starting points to use for constrained 170 | %optimization, which is time consuming and computationally intensive 171 | fprintf('\nDetermining best %d out of %d starting points for optimization algorithm\n',optimnum,searchnum) 172 | search = search_param_space(data,searchnum,optimnum,model,searchpoints,pret_cost_options); 173 | fprintf('Best %d starting points found\n',optimnum) 174 | 175 | %create a model structure for each optimization to be completed (enables 176 | %use of parfor loop) 177 | modelstate(optimnum) = model; 178 | for op = 1:optimnum 179 | modelstate(op) = model; 180 | modelstate(op).ampvals = search.ampvals(op,:); 181 | modelstate(op).boxampvals = search.boxampvals(op,:); 182 | modelstate(op).latvals = search.latvals(op,:); 183 | modelstate(op).tmaxval = search.tmaxvals(op,:); 184 | modelstate(op).yintval = search.yintvals(op,:); 185 | modelstate(op).slopeval = search.slopevals(op,:); 186 | end 187 | 188 | %perform constrained optimization on the best points from the coarse 189 | %parameter space search 190 | searchoptims = struct('eventtimes',model.eventtimes,'boxtimes',{model.boxtimes},'samplerate',model.samplerate,'window',model.window,'ampvals',[],'boxampvals',[],'latvals',[],'tmaxval',[],'yintval',[],'slopeval',[],'numparams',[],'cost',[],'R2',[],'BICrel',[]); 191 | tempcosts = nan(optimnum,1); 192 | if wnum == 1 193 | fprintf('\nBeginning optimization of best starting points\nOptims completed: ') 194 | for op = 1:optimnum 195 | searchoptims(op) = pret_optim(data,modelstate(op).samplerate,modelstate(op).window,modelstate(op),pret_optim_options); 196 | tempcosts(op) = searchoptims(op).cost; 197 | fprintf('%d ',op) 198 | end 199 | else 200 | p = gcp('nocreate'); 201 | if isempty(p) 202 | parpool(wnum); 203 | end 204 | fprintf('\nBeginning optimization of best starting points\nOptims completed: ') 205 | parfor op = 1:optimnum 206 | searchoptims(op) = pret_optim(data,modelstate(op).samplerate,modelstate(op).window,modelstate(op),pret_optim_options); 207 | tempcosts(op) = searchoptims(op).cost; 208 | fprintf('%d ',op) 209 | end 210 | end 211 | 212 | fprintf('\nOptimizations completed!\n\n') 213 | [~,minind] = min(tempcosts); 214 | estim = searchoptims(minind); 215 | 216 | function search = search_param_space(data,searchnum,optimnum,modelstate,params,pret_cost_options) 217 | 218 | costs = nan(searchnum,1); 219 | for ss = 1:searchnum 220 | modelstate.ampvals = params.ampvals(ss,:); 221 | modelstate.latvals = params.latvals(ss,:); 222 | modelstate.tmaxval = params.tmaxvals(ss); 223 | modelstate.yintval = params.yintvals(ss); 224 | modelstate.slopeval = params.slopevals(ss); 225 | modelstate.boxampvals = params.boxampvals(ss,:); 226 | costs(ss) = pret_cost(data,modelstate.samplerate,modelstate.window,modelstate,pret_cost_options); 227 | end 228 | 229 | [~,sortind] = sort(costs); 230 | [~,rank] = sort(sortind); 231 | optimindex = find(rank <= optimnum); 232 | 233 | search.ampvals = params.ampvals(optimindex,:); 234 | search.latvals = params.latvals(optimindex,:); 235 | search.tmaxvals = params.tmaxvals(optimindex,:); 236 | search.yintvals = params.yintvals(optimindex,:); 237 | search.slopevals = params.slopevals(optimindex,:); 238 | search.boxampvals = params.boxampvals(optimindex,:); 239 | search.optimindex = optimindex; 240 | search.costs = costs; 241 | 242 | end 243 | 244 | end 245 | -------------------------------------------------------------------------------- /pret_estimate_sj.m: -------------------------------------------------------------------------------- 1 | function sj = pret_estimate_sj(sj,model,wnum,options) 2 | % pret_estimate_sj 3 | % sj = pret_estimate_sj(sj,models,wnum) 4 | % sj = pret_estimate_sj(sj,models,wnum,options) 5 | % 6 | % Performs the parameter estimation procedure for each set of data that has 7 | % been preprocessed/organized into a sj structure by pret_preprocess. 8 | % 9 | % Inputs: 10 | % 11 | % sj = structure output by pret_preprocess containing data in a format 12 | % that pret_estimate_sj uses. 13 | % 14 | % model = model structure created by pret_model and filled in by user. 15 | % Parameter values in model.ampvals, model.boxampvals, model.latvals, 16 | % model.tmaxval, model.yintval, and model.slopeval do not need to be 17 | % provided if they are being estimated but should be provided if they 18 | % are not being estimated. 19 | % *NOTE - if you want to fit multiple models, you can input an 20 | % Nx1 structure with the same fields as "model", where N is the 21 | % number of models and each element is a separate model 22 | % structure* 23 | % 24 | % wnum = number of workers used by matlab's parallel pool to complete 25 | % the process (parpool will not be initialized if set to 1). 26 | % 27 | % options = options structure for pret_estimate_sj. Default options can be 28 | % returned by calling this function with no arguments, or see 29 | % pret_default_options. 30 | % 31 | % Output 32 | % 33 | % sj = same structure that was input, but with the additional field 34 | % appended: 35 | % optim = a structure with fields titled after the condition 36 | % labels. Each of these fields is an 1xN structure, where N is 37 | % the number of models in the "model" input. Each element in 38 | % these structures is an optim structure output by pret_estimate 39 | % fitting that condition with a single model. For more 40 | % information about this structure, see pret_estimate. 41 | % 42 | % Options 43 | % 44 | % pret_estimate_options = options structure for pret_estimate, 45 | % which pret_estimate_sj uses to perform parameter estimation for each 46 | % set of data in sj. 47 | % 48 | % trialmode = 'mean' to fit the trial means or 'single' to fit single 49 | % trials simultaneously (default = 'mean') 50 | % 51 | % saveflag (true/false) = save a .mat file with the output sj 52 | % variable? 53 | % 54 | % savefile = if saveflag true, save .mat file to this dir (include 55 | % name of course) 56 | % 57 | % pret_model_check = options for pret_model_check 58 | % 59 | % 60 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 61 | % 62 | % This program is free software: you can redistribute it and/or modify 63 | % it under the terms of the GNU General Public License as published by 64 | % the Free Software Foundation, either version 3 of the License, or 65 | % (at your option) any later version. 66 | % 67 | % This program is distributed in the hope that it will be useful, 68 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 69 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 70 | % GNU General Public License for more details. 71 | % 72 | % You should have received a copy of the GNU General Public License 73 | % along with this program. If not, see . 74 | % 75 | 76 | if nargin < 4 77 | opts = pret_default_options(); 78 | options = opts.pret_estimate_sj; 79 | clear opts 80 | if nargin < 1 81 | sj = options; 82 | return 83 | end 84 | end 85 | 86 | %OPTIONS 87 | pret_estimate_options = options.pret_estimate; 88 | trialmode = options.trialmode; 89 | saveflag = options.saveflag; 90 | savefile = options.savefile; 91 | pret_model_check_options = options.pret_model_check; 92 | 93 | %check models 94 | for mm = 1:length(model) 95 | try 96 | pret_model_check(model(mm),pret_model_check_options) 97 | catch 98 | fprintf('\nError in model %d\n',mm) 99 | pret_model_check(model(mm),pret_model_check_options) 100 | end 101 | end 102 | 103 | sj.estim = []; 104 | 105 | for mm = 1:length(model) 106 | fprintf('\nModel %d\n',mm) 107 | for cc = 1:length(sj.conditions) 108 | cond = sj.conditions{cc}; 109 | fprintf('Condition %s\n',cond) 110 | switch trialmode 111 | case 'mean' 112 | data = sj.means.(cond); 113 | case 'single' 114 | data = sj.(cond); 115 | otherwise 116 | error('"trialmode" not recognized') 117 | end 118 | sj.estim(mm).(cond) = pret_estimate(data, ... 119 | sj.samplerate, sj.trialwindow, model(mm), wnum, pret_estimate_options); 120 | 121 | if saveflag 122 | save(savefile,'sj') 123 | end 124 | end 125 | end -------------------------------------------------------------------------------- /pret_fake_data.m: -------------------------------------------------------------------------------- 1 | function [data, outparams] = pret_fake_data(numtseries,parammode,samplerate,trialwindow,model,options) 2 | % pret_fake_data 3 | % data = pret_fake_data(numtseries,parammode,samplerate,trialwindow,model) 4 | % data = pret_fake_data(numtseries,parammode,samplerate,trialwindow,model,options) 5 | % 6 | % Generates fake pupil size time series using randomly generated parameters for 7 | % a given model. 8 | % 9 | % Inputs: 10 | % 11 | % numtseries = number of time series to generate 12 | % 13 | % parammode ('uniform', 'normal', or 'space_optimal') = mode used to 14 | % generate parameters. 15 | % 'uniform' - parameters are attempted to be sampled evenly 16 | % from the range of their respective bounds. When the number of 17 | % points sampled is relatively low, binning can help span the 18 | % parameter space. For each parameter, the range is split up 19 | % into options.nbins number of bins and a floor(num/nbins) number 20 | % of points is randomly and uniformly sampled from each bin. The 21 | % remaining points are then sampled from the entire range. 22 | % 'normal' - parameters are drawn from a normal distrubtion 23 | % centered around the values provided in the input model 24 | % structure. The standard deviation is set by options.sigma. 25 | % 26 | % samplerate = sampling rate of data in Hz. Can be different than the 27 | % value in the model.samplerate if desired. 28 | % 29 | % trialwindow = a 2 element vector containing the starting and ending 30 | % times (in ms) of the trial epoch. Can be different than 31 | % model.window if desired. 32 | % 33 | % model = model structure created by pret_model and filled in by user. 34 | % *IMPORTANT - parameter values in model.ampvals, 35 | % model.boxampvals, model.latvals, model.tmaxval, model.yintval, 36 | % and model.slopeval must be provided if 'normal' parammode is used! 37 | % 38 | % options = options structure for pret_fake_data. Default options 39 | % can be returned by calling this function with no arguments, or see 40 | % pret_default_options. 41 | % 42 | % Outputs: 43 | % 44 | % data = a 2D matrix where each row is a generated pupil size time 45 | % series. 46 | % 47 | % outparams = an output structure containing all sets of parameters 48 | % generated to create fake data. Contains the following fields: 49 | % ampvals = 2D matrix of generated event amplitude parameters. 50 | % Each row is one set of parameters. 51 | % boxampvals = 2D matrix of generated box amplitude parameters. 52 | % latvals = 2D matrix of generated event latency parameters. 53 | % tmaxvals = column vector of generated tmax parameters. 54 | % yintvals = column vector of generated y-intercept parameters. 55 | % slopevals = column vector of generated slope parameters. 56 | % 57 | % 58 | % Options 59 | % 60 | % pret_generate_params = options structure for pret_generate_params, 61 | % which pret_fake_data uses to generate parameter values that the 62 | % artificial time series are constructed from. Options for the 63 | % various "parammode" options are in here. 64 | % 65 | % pret_model_check = options for pret_model_check 66 | % 67 | % 68 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 69 | % 70 | % This program is free software: you can redistribute it and/or modify 71 | % it under the terms of the GNU General Public License as published by 72 | % the Free Software Foundation, either version 3 of the License, or 73 | % (at your option) any later version. 74 | % 75 | % This program is distributed in the hope that it will be useful, 76 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 77 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 78 | % GNU General Public License for more details. 79 | % 80 | % You should have received a copy of the GNU General Public License 81 | % along with this program. If not, see . 82 | % 83 | 84 | if nargin < 6 85 | opts = pret_default_options(); 86 | options = opts.pret_fake_data; 87 | clear opts 88 | if nargin < 1 89 | data = options; 90 | return 91 | end 92 | end 93 | 94 | %OPTIONS 95 | pret_generate_params_options = options.pret_generate_params; 96 | pret_model_check_options = options.pret_model_check; 97 | 98 | %check inputs 99 | pret_model_check(model,pret_model_check_options) 100 | 101 | sfact = samplerate/1000; 102 | time = trialwindow(1):1/sfact:trialwindow(2); 103 | 104 | %model time window vs time points 105 | if ~(any(trialwindow(1) == time)) || ~(any(trialwindow(2) == time )) 106 | error('Given trial window does not fall on time points according to sample rate and trial window') 107 | end 108 | 109 | data = nan(numtseries,length(time)); 110 | modelstate = model; 111 | modelstate.window = trialwindow; 112 | modelstate.samplerate = samplerate; 113 | 114 | 115 | %generate parameters to create time series with 116 | params = pret_generate_params(numtseries,parammode,model,pret_generate_params_options); 117 | 118 | %generate time series from parameters 119 | for ts = 1:numtseries 120 | if ~isempty(model.eventtimes) 121 | modelstate.ampvals = params.ampvals(ts,:); 122 | modelstate.latvals = params.latvals(ts,:); 123 | modelstate.tmaxval = params.tmaxvals(ts); 124 | modelstate.yintval = params.yintvals(ts); 125 | modelstate.slope = params.slopevals(ts); 126 | end 127 | if ~isempty(model.boxtimes) 128 | modelstate.boxampvals = params.boxampvals(ts,:); 129 | end 130 | data(ts,:) = pret_calc(modelstate); 131 | end 132 | 133 | outparams = struct('eventimes',model.eventtimes,'boxtimes',model.boxtimes,'ampvals',params.ampvals,'boxampvals',params.boxampvals,'latvals',params.latvals,'tmaxvals',params.tmaxvals,'yintvals',params.yintvals,'slopevals',params.slopevals); 134 | 135 | -------------------------------------------------------------------------------- /pret_generate_params.m: -------------------------------------------------------------------------------- 1 | function params = pret_generate_params(num,parammode,model,options) 2 | % pret_generate_params 3 | % params = pret_generate_params(num, parammode, model) 4 | % params = pret_generate_params(num, parammode, model,options) 5 | % options = pret_generate_params() 6 | % 7 | % Generates random parameters using the specifications of a given model. 8 | % 9 | % Inputs: 10 | % 11 | % num = number of sets of parameters to be generated. 12 | % 13 | % parammode ('uniform', 'normal', or 'space_optimal') = mode used to 14 | % generate parameters. 15 | % 'uniform' - parameters are attempted to be sampled evenly 16 | % from the range of their respective bounds. When the number of 17 | % points sampled is relatively low, binning can help span the 18 | % parameter space. For each parameter, the range is split up 19 | % into options.nbins number of bins and a floor(num/nbins) number 20 | % of points is randomly and uniformly sampled from each bin. The 21 | % remaining points are then sampled from the entire range. 22 | % 'normal' - parameters are drawn from a normal distrubtion 23 | % centered around the values provided in the input model 24 | % structure. The standard deviation is set by options.sigma. 25 | % 26 | % model = model structure created by pret_model and filled in by user. 27 | % *IMPORTANT - parameter values in model.ampvals, 28 | % model.boxampvals, model.latvals, model.tmaxval, model.yintval, 29 | % and model.slopeval must be provided if 'normal' parammode is used! 30 | % 31 | % options = options structure for pret_generate_params. Default options 32 | % can be returned by calling this function with no arguments, or see 33 | % pret_default_options. 34 | % 35 | % Outputs: 36 | % 37 | % params = an output structure containing all sets of parameters 38 | % generated. Contains the following fields: 39 | % ampvals = 2D matrix of generated event amplitude parameters. 40 | % Each row is one set of parameters. 41 | % boxampvals = 2D matrix of generated box amplitude parameters. 42 | % latvals = 2D matrix of generated event latency parameters. 43 | % tmaxvals = column vector of generated tmax parameters. 44 | % yintvals = column vector of generated y-intercept parameters. 45 | % slopevals = column vector of generated slope parameters. 46 | % 47 | % 48 | % Options 49 | % 50 | % nbins (50) = if 'uniform' parammode is used, specifies the number of 51 | % bins that are sampled from across the range of each parameter. 52 | % 53 | % sigma (0.05) = if 'normal' parammode is used, specifies the standard 54 | % deviation of the normal distribution each parameter is drawn from. 55 | % This standard deviation is applied differentially to each parameter 56 | % using their respective scaling factors (below). 57 | % 58 | % ampfact (1/10), boxampfact (1/10), latfact (1/1000), tmaxfact (1/1000), 59 | % yintfact (1/10), slopefact (100) = if 'normal' parammode is used, 60 | % these options specify the scaling factors for amplitude, latency, 61 | % tmax, y-intercept, and slope parameters respectively. This is 62 | % necessary to apply a single sigma value to different parameters 63 | % that have varying orders of magntitude. 64 | % 65 | % pret_model_check = options for pret_model_check 66 | % 67 | % 68 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 69 | % 70 | % This program is free software: you can redistribute it and/or modify 71 | % it under the terms of the GNU General Public License as published by 72 | % the Free Software Foundation, either version 3 of the License, or 73 | % (at your option) any later version. 74 | % 75 | % This program is distributed in the hope that it will be useful, 76 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 77 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 78 | % GNU General Public License for more details. 79 | % 80 | % You should have received a copy of the GNU General Public License 81 | % along with this program. If not, see . 82 | % 83 | 84 | if nargin < 4 85 | opts = pret_default_options(); 86 | options = opts.pret_generate_params; 87 | clear opts 88 | if nargin < 1 89 | params = options; 90 | return 91 | end 92 | end 93 | 94 | %OPTIONS 95 | nbins = options.nbins; 96 | sigma = options.sigma; 97 | ampfact = options.ampfact; 98 | boxampfact = options.boxampfact; 99 | latfact = options.latfact; 100 | tmaxfact = options.tmaxfact; 101 | yintfact = options.yintfact; 102 | slopefact = options.slopefact; 103 | pret_model_check_options = options.pret_model_check; 104 | 105 | %check inputs 106 | pret_model_check(model,pret_model_check_options) 107 | 108 | if ~((round(num) == num) && num > 0) 109 | error('Input "num" must be positive integer') 110 | end 111 | 112 | %set random number generator 113 | rng(0) 114 | 115 | params = struct('blank',[]); 116 | 117 | params.ampvals = nan(num,length(model.eventtimes)); 118 | params.latvals = nan(num,length(model.eventtimes)); 119 | params.tmaxvals = nan(num,1); 120 | params.yintvals = nan(num,1); 121 | params.slopevals = nan(num,1); 122 | params.boxampvals = nan(num,length(model.boxtimes)); 123 | 124 | params = rmfield(params,'blank'); 125 | 126 | switch parammode 127 | case 'uniform' 128 | 129 | %amplitude 130 | if model.ampflag 131 | for cc = 1:length(model.eventtimes) 132 | params.ampvals(:,cc) = unibin(num,model.ampbounds(1,cc),model.ampbounds(2,cc),nbins); 133 | end 134 | else 135 | params.ampvals = repmat(model.ampvals,num,1); 136 | end 137 | 138 | %latency 139 | if model.latflag 140 | for cc = 1:length(model.eventtimes) 141 | params.latvals(:,cc) = unibin(num,model.latbounds(1,cc),model.latbounds(2,cc),nbins); 142 | end 143 | else 144 | params.latvals = repmat(model.latvals,num,1); 145 | end 146 | 147 | %tmax 148 | if model.tmaxflag 149 | params.tmaxvals = unibin(num,model.tmaxbounds(1),model.tmaxbounds(2),nbins); 150 | else 151 | params.tmaxvals = repmat(model.tmaxval,num,1); 152 | end 153 | 154 | %y-intercept 155 | if model.yintflag 156 | params.yintvals = unibin(num,model.yintbounds(1),model.yintbounds(2),nbins); 157 | else 158 | params.yintvals = repmat(model.yintval,num,1); 159 | end 160 | 161 | %slope 162 | if model.slopeflag 163 | params.slopevals = unibin(num,model.slopebounds(1),model.slopebounds(2),nbins); 164 | else 165 | params.slopevals = repmat(model.slopeval,num,1); 166 | end 167 | 168 | %box amplitude 169 | if model.boxampflag 170 | for cc = 1:length(model.boxtimes) 171 | params.boxampvals(:,cc) = unibin(num,model.boxampbounds(1,cc),model.boxampbounds(2,cc),nbins); 172 | end 173 | else 174 | params.boxampvals = repmat(model.boxampvals,num,1); 175 | end 176 | 177 | case 'normal' 178 | 179 | if model.ampflag 180 | ampvals = model.ampvals .* ampfact; 181 | for cc = 1:length(model.eventtimes) 182 | params.ampvals(:,cc) = (ampvals(cc) + randn(num,1) .* sigma) .* 1/ampfact; 183 | end 184 | params.ampvals(params.ampvals < model.ampbounds(1,cc)) = model.ampbounds(1,cc); 185 | params.ampvals(params.ampvals > model.ampbounds(2,cc)) = model.ampbounds(2,cc); 186 | else 187 | params.ampvals = repmat(model.ampvals,num,1); 188 | end 189 | 190 | if model.latflag 191 | latvals = model.latvals .* latfact; 192 | for cc = 1:length(model.eventtimes) 193 | params.latvals(:,cc) = (latvals(cc) + randn(num,1) .* sigma) .* 1/latfact; 194 | end 195 | params.latvals(params.latvals < model.latbounds(1,cc)) = model.latbounds(1,cc); 196 | params.latvals(params.latvals > model.latbounds(2,cc)) = model.latbounds(2,cc); 197 | else 198 | params.latvals = repmat(model.latvals,num,1); 199 | end 200 | 201 | if model.tmaxflag 202 | tmaxval = model.tmaxval .* tmaxfact; 203 | params.tmaxvals = (tmaxval + randn(num,1) .* sigma) .* 1/tmaxfact; 204 | params.tmaxvals(params.tmaxvals < model.tmaxbounds(1)) = model.tmaxbounds(1); 205 | params.tmaxvals(params.tmaxvals > model.tmaxbounds(2)) = model.tmaxbounds(2); 206 | else 207 | params.tmaxvals = repmat(model.tmaxval,num,1); 208 | end 209 | 210 | if model.yintflag 211 | yintval = model.yintval .* yintfact; 212 | params.yintvals = (yintval + randn(num,1) .* sigma) .* 1/yintfact; 213 | params.yintvals(params.yintvals < model.yintbounds(1)) = model.yintbounds(1); 214 | params.yintvals(params.yintvals > model.yintbounds(2)) = model.yintbounds(2); 215 | else 216 | params.yintvals = repmat(model.yintval,num,1); 217 | end 218 | 219 | if model.slopeflag 220 | slopeval = model.slopeval .* slopefact; 221 | params.slopevals = (slopeval + randn(num,1) .* sigma) .* 1/slopefact; 222 | params.slopevals(params.slopevals < model.slopebounds(1)) = model.slopebounds(1); 223 | params.slopevals(params.slopevals > model.slopebounds(2)) = model.slopebounds(2); 224 | else 225 | params.slopevals = repmat(model.slopeval,num,1); 226 | end 227 | 228 | if model.boxampflag 229 | boxampvals = model.boxampvals .* boxampfact; 230 | for cc = 1:length(model.boxtimes) 231 | params.boxampvals(:,cc) = (boxampvals(cc) + randn(num,1) .* sigma) .* 1/boxampfact; 232 | end 233 | params.boxampvals(params.boxampvals < model.boxampbounds(1,cc)) = model.boxampbounds(1,cc); 234 | params.boxampvals(params.boxampvals > model.boxampbounds(2,cc)) = model.boxampbounds(2,cc); 235 | else 236 | params.boxampvals = repmat(model.boxampvals,num,1); 237 | end 238 | 239 | otherwise 240 | error('Input "parammode" not recognized.') 241 | end 242 | 243 | function dist = unibin(num,lb,ub,nbins) 244 | %generates a kind of uniform distrubution of num numbers by defining a range, 245 | %splitting that range into nbins number of bins, then using rand to 246 | %generate floor(num/nbins) in each bin. The remainder of this 247 | %division is sampled from across the entire range. 248 | pbb = floor(num/nbins); 249 | rmn = rem(num,nbins); 250 | temp = nan(num,1); 251 | bins = linspace(lb,ub,nbins+1); 252 | for b = 1:nbins 253 | temp(pbb*(b-1)+1:pbb*b) = (bins(b+1)-bins(b)).*rand(pbb,1) + bins(b); 254 | end 255 | if rmn ~= 0 256 | temp(num-rmn+1:num) = (ub-lb).*rand(rmn,1) + lb; 257 | end 258 | dist = randsample(temp,num); 259 | end 260 | 261 | end 262 | -------------------------------------------------------------------------------- /pret_model.m: -------------------------------------------------------------------------------- 1 | function model = pret_model() 2 | % pret_model 3 | % model = pret_model() 4 | % 5 | % Creates a structure with all of the fields that must be filled in by the 6 | % user to create a specific model. This structure provides information to 7 | % functions about the form of the model. 8 | % 9 | % 10 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 11 | % 12 | % This program is free software: you can redistribute it and/or modify 13 | % it under the terms of the GNU General Public License as published by 14 | % the Free Software Foundation, either version 3 of the License, or 15 | % (at your option) any later version. 16 | % 17 | % This program is distributed in the hope that it will be useful, 18 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | % GNU General Public License for more details. 21 | % 22 | % You should have received a copy of the GNU General Public License 23 | % along with this program. If not, see . 24 | % 25 | 26 | %% preallocate model 27 | model = struct('window',[]); 28 | 29 | %% model time window and sample rate 30 | % a two element vector delineating the end and start times (in ms) for the 31 | % model (can be different than the trialwindow given for actual data) 32 | % > for example, if your trials are epoched -500 to 3500 ms, you might 33 | % only be interested in modeling 0 to 3500 ms 34 | model.window = []; 35 | 36 | % the sample rate (in Hz) you would like the model to work with (the 37 | % samplerate in a sj structure will be used instead if used in a function 38 | % that uses them) 39 | model.samplerate = []; 40 | 41 | %% set parameters on/off for model 42 | model.ampflag = true; %event amplitude to be estimated as a parameter: true/false 43 | model.boxampflag = true; %box amplitude to be estimated as a parameter: true/false 44 | model.latflag = true; %latency to be estimated as a parameter (not for boxes): true/false 45 | model.tmaxflag = true; %tmax to be estimated as a parameter: true/false 46 | model.yintflag = true; %y-intercept to be estimated as a parameter: true/false 47 | model.slopeflag = true; 48 | 49 | %% define instantaneous events/box regressors 50 | % vectors containing the time of occurrence (in ms) for each 51 | % instantaneous event and cell array of corresponding labels (optional) 52 | model.eventtimes = []; 53 | model.eventlabels = {}; 54 | 55 | % cell array of two element vectors, each containing the start and end 56 | % times (in ms) for each box function regressor and cell array of 57 | % corresponding labels (optional) 58 | % **** Should the latency of this be allowed to be estimated? **** 59 | model.boxtimes = {}; 60 | model.boxlabels = {}; 61 | 62 | %% define parameter boundaries for constrained optimization 63 | % 2 by N matrices which contain the lower and upper bounds of each events's 64 | % and each boxes's amplitude value for the constrained optimization. N is the 65 | % number of events or boxes. The lower bounds are in the first row and the 66 | % upper bounds are in the second row. 67 | % (not important if event or box amplitude not to be estimated) 68 | model.ampbounds = []; 69 | model.boxampbounds = []; 70 | 71 | % a 2 by N matrix which contains the lower and upper bounds of each events's 72 | % latency for the constrained optimization. N is the number of events. 73 | % The lower bounds are in the first row and the upper bounds are in the 74 | % second row. (not important if latency not to be estimated) 75 | % REMINDER: latency refers to the time shift (in ms) relative to a 76 | % regressor's actual event time (entered in model.eventtimes), NOT the actual 77 | % time values of the event (a value of 0 means pupil response starts at the 78 | % same time as the actual event) 79 | model.latbounds = []; 80 | 81 | % two element vectors containing the lower and upper bounds (in that 82 | % order) of the tmax and y-intercept values for the constrained optimization 83 | % (not important if tmax and/or y-intercept not to be estimated) 84 | model.tmaxbounds = []; 85 | model.yintbounds = []; 86 | model.slopebounds = []; 87 | 88 | 89 | %% define default parameter values 90 | % vectors of default amplitude values, one for each event and box regressor 91 | % (must be provided if not being estimated as a parameter in the model) 92 | model.ampvals = []; 93 | model.boxampvals = []; 94 | 95 | % vector of default latency values, one for each event 96 | % (must be provided if not being estimated as a parameter) 97 | model.latvals = []; 98 | 99 | % default tmax (in ms) and y-intercept values (only one value each) (must 100 | % be provided if not being estimated) 101 | model.tmaxval = []; 102 | model.yintval = []; 103 | model.slopeval = []; 104 | 105 | -------------------------------------------------------------------------------- /pret_model_check.m: -------------------------------------------------------------------------------- 1 | function pret_model_check(model,options) 2 | % pret_model_check 3 | % pret_model_check(model) 4 | % pret_model_check(model,options) 5 | % 6 | % Checks if the specifications in "model" are valid. If a model is not 7 | % valid, will throw an error. Otherwise, if a model is valid, no error will 8 | % be thrown. 9 | % 10 | % Inputs: 11 | % 12 | % model = model structure to be checked 13 | % 14 | % Options: 15 | % 16 | % checkparams: (true/false) = check if parameter values filled out, even 17 | % for models where those parameters are being fit (default = false) 18 | % 19 | % 20 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 21 | % 22 | % This program is free software: you can redistribute it and/or modify 23 | % it under the terms of the GNU General Public License as published by 24 | % the Free Software Foundation, either version 3 of the License, or 25 | % (at your option) any later version. 26 | % 27 | % This program is distributed in the hope that it will be useful, 28 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 29 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 | % GNU General Public License for more details. 31 | % 32 | % You should have received a copy of the GNU General Public License 33 | % along with this program. If not, see . 34 | % 35 | 36 | if nargin < 2 37 | opts = pret_default_options(); 38 | options = opts.pret_model_check; 39 | clear opts 40 | end 41 | 42 | %OPTIONS 43 | checkparams = options.checkparams; 44 | 45 | %% window 46 | if length(model.window) ~= 2 47 | error('model.window must be a two element vector') 48 | end 49 | 50 | %% samplerate 51 | if length(model.samplerate) ~= 1 52 | error('model.samplerate must be provided as a single value') 53 | end 54 | 55 | sfact = model.samplerate/1000; 56 | time = model.window(1):1/sfact:model.window(2); 57 | if ~(any(model.window(1) == time)) || ~(any(model.window(2) == time)) 58 | error('"model.window" not compatible with "model.samplerate"') 59 | end 60 | 61 | %% event times 62 | if isempty(model.eventtimes) && (model.ampflag || model.latflag) 63 | error('If event amplitude and/or event latency is to be estimated,\nevent times must be provided in model.eventtimes') 64 | end 65 | 66 | %% box times 67 | if isempty(model.boxtimes) && model.boxampflag 68 | error('If box amplitude is to be estimated, box times must be provided') 69 | end 70 | 71 | %% event amplitude 72 | if model.ampflag 73 | if ~isempty(model.eventtimes) 74 | if length(model.eventtimes) ~= size(model.ampbounds,2) 75 | error('Number of event amplitude bounds in model not equal to number of events') 76 | end 77 | if checkparams 78 | if length(model.eventtimes) ~= length(model.ampvals) 79 | error('Number of default event amplitudes not equal to number of events') 80 | end 81 | for ii = 1:length(model.eventtimes) 82 | if (model.ampvals(ii) < model.ampbounds(1,ii)) || (model.ampvals(ii) > model.ampbounds(2,ii)) 83 | error('At least one given amplitude value in model.ampvals is outside of its\nbounds according to the info in model.ampbounds') 84 | end 85 | end 86 | end 87 | end 88 | else 89 | if length(model.eventtimes) ~= length(model.ampvals) 90 | error('Number of defualt event amplitudes not equal to number of events') 91 | end 92 | end 93 | 94 | %% box amplitude 95 | if ~isempty(model.boxtimes) 96 | for ii = 1:length(model.boxtimes) 97 | if length(model.boxtimes{ii}) ~= 2 98 | error('All cells in boxtimes must be a 2 element vector') 99 | end 100 | if ~(any(model.boxtimes{ii}(1) == time)) || ~(any(model.boxtimes{ii}(2) == time)) 101 | if (rem(model.boxtimes{ii}(2)-time(end),1/sfact) == 0) && (rem(model.boxtimes{ii}(1)-time(1),1/sfact) == 0) 102 | % box times can fall outside model.window, but must be on 103 | % the time vector if it were to be extended 104 | else 105 | error('Box %d start and end time points do not fall on time vector defined by\nmodel.window and model.samplerate',ii) 106 | end 107 | end 108 | end 109 | end 110 | if model.boxampflag 111 | if ~isempty(model.boxtimes) 112 | if length(model.boxtimes) ~= size(model.boxampbounds,2) 113 | error('Number of box amplitude bounds in model not equal to number of boxes') 114 | end 115 | if checkparams 116 | if length(model.boxtimes) ~= length(model.boxampvals) 117 | error('\nNumber of default box amplitude values does not match number of boxes\n') 118 | end 119 | for ii = 1:length(model.boxtimes) 120 | if (model.boxampvals(ii) < model.boxampbounds(1,ii)) || (model.boxampvals(ii) > model.boxampbounds(2,ii)) 121 | error('At least one given box amplitude value in model.boxampvals is outside of its\nbounds according to the info in model.boxampbounds') 122 | end 123 | end 124 | end 125 | end 126 | else 127 | if length(model.boxtimes) ~= length(model.boxampvals) 128 | error('Number of default box amplitudes not equal to number of boxes') 129 | end 130 | end 131 | 132 | %% event latency 133 | if model.latflag 134 | if ~isempty(model.eventtimes) 135 | if length(model.eventtimes) ~= size(model.latbounds,2) 136 | error('Number of event latency bounds in model not equal to number of events') 137 | end 138 | if checkparams 139 | if length(model.eventtimes) ~= length(model.latvals) 140 | error('\nNumber of default event latency values does not match number of events\n') 141 | end 142 | for ii = 1:length(model.eventtimes) 143 | if (model.latvals(ii) < model.latbounds(1,ii)) || (model.latvals(ii) > model.latbounds(2,ii)) 144 | error('At least one given latency value in model.latvals is outside of its\nbounds according to the info in model.latbounds') 145 | end 146 | end 147 | end 148 | end 149 | else 150 | if length(model.eventtimes) ~= length(model.latvals) 151 | error('Number of default event latency not equal to number of events') 152 | end 153 | end 154 | 155 | %% tmax 156 | if model.tmaxflag 157 | if length(model.tmaxbounds) ~= 2 158 | error('model.tmaxbounds should be a 2 element vector') 159 | end 160 | if checkparams 161 | if length(model.tmaxval) ~= 1 162 | error('\nNumber of default tmax values not equal to 1\n') 163 | end 164 | if (model.tmaxval < model.tmaxbounds(1)) || (model.tmaxval > model.tmaxbounds(2)) 165 | error('Given tmax value in model.tmaxval is outside of its\nbounds according to the info in model.tmaxbounds') 166 | end 167 | end 168 | else 169 | if length(model.tmaxval) ~= 1 170 | error('Number of default tmax values not equal to 1') 171 | end 172 | end 173 | 174 | %% y-intercept 175 | if model.yintflag 176 | if length(model.yintbounds) ~= 2 177 | error('model.yintbounds should be a 2 element vector') 178 | end 179 | if checkparams 180 | if length(model.yintval) ~= 1 181 | error('Number of default y-intercept values not equal to 1') 182 | end 183 | if (model.yintval < model.yintbounds(1)) || (model.yintval > model.yintbounds(2)) 184 | error('Given yint value in model.yintval is outside of its\nbounds according to the info in model.yintbounds') 185 | end 186 | end 187 | else 188 | if length(model.yintval) ~= 1 189 | error('Number of default y-intercept values not equal to 1') 190 | end 191 | end 192 | 193 | %% slope 194 | if model.slopeflag 195 | if length(model.slopebounds) ~= 2 196 | error('model.slopebounds should be a 2 element vector') 197 | end 198 | if checkparams 199 | if length(model.slopeval) ~= 1 200 | error('Number of default slope values not equal to 1') 201 | end 202 | if (model.slopeval < model.slopebounds(1)) || (model.slopeval > model.slopebounds(2)) 203 | error('Given slope value in model.slopeval is outside of its\nbounds according to the info in model.slopebounds') 204 | end 205 | end 206 | else 207 | if length(model.slopeval) ~= 1 208 | error('Number of default slope values not equal to 1') 209 | end 210 | end 211 | -------------------------------------------------------------------------------- /pret_optim.m: -------------------------------------------------------------------------------- 1 | function optim = pret_optim(data,samplerate,trialwindow,model,options) 2 | % pret_optim 3 | % optim = pret_optim(data,samplerate,trialwindow,model) 4 | % optim = pret_optim(data,samplerate,trialwindow,model,options) 5 | % options = pret_optim() 6 | % 7 | % Constrained optimization to find the model parameters for a given model 8 | % that best matches the time series input as "data". Performs a single 9 | % optimization using the parameter values in "model" as the starting point. 10 | % 11 | % *NOTE: It is recommended to use pret_estimate, which performs an initial 12 | % coarse search and then selects the best starting points for optimization 13 | % with pret_optim.* 14 | % 15 | % Inputs: 16 | % 17 | % data = a single pupil size time series as a row vector OR an MxN 18 | % matrix with M time series. If a matrix, will compute single set of 19 | % parameters minimizing the cost for all time series. 20 | % 21 | % samplerate = sampling rate of data in Hz. 22 | % 23 | % trialwindow = a 2 element vector containing the starting and ending 24 | % times (in ms) of the trial epoch. 25 | % 26 | % model = model structure created by pret_model and filled in by user. 27 | % Parameter values in model.ampvals, model.boxampvals, model.latvals, 28 | % model.tmaxval, model.yintval, and model.slopeval MUST be provided. 29 | % These values are the starting point for the optimization. 30 | % 31 | % options = options structure for pret_optim. Default options can be 32 | % returned by calling this function with no arguments. 33 | % 34 | % Outputs: 35 | % 36 | % optim = a structure containing the parameters estimated by fmincon 37 | % with the following fileds: 38 | % eventtimes = a copy of eventtimes from "model". 39 | % boxtimes = a copy of boxtimes from "model". 40 | % samplerate = a copy of samplerate from "model". 41 | % window = a copy of window from "model". 42 | % ampvals = the event amplitude values fit by fmincon. 43 | % boxampvals = the box regressor amplitude values fit. 44 | % latvals = the event latency values fit. 45 | % tmaxval = the tmax value fit. 46 | % yintval = the y-intercept value fit. 47 | % slopeval = the slope value fit. 48 | % numparams = number of parameters fit. 49 | % cost = the sum of square errors between the optimized 50 | % parameters and the actual data. 51 | % R2 = the R^2 goodness of fit value. 52 | % BICrel = the relative BIC value of the model fit 53 | % *relative because we use the guassian simplfictation of the 54 | % BIC 55 | % *since it is relative, only use to compare models/fits on 56 | % data from the same task 57 | % 58 | % *Note - "optim" can be input into pret_plot_model and pret_calc in 59 | % place of the "model" input* 60 | % 61 | % Options 62 | % 63 | % optimplotflag (true/false) = plot optimization in realtime? Set 64 | % to false if you want speed. 65 | % 66 | % pret_cost = options structure for pret_cost, which pret_optim uses 67 | % as the cost function for fmincon. 68 | % 69 | % ampfact (1/10) = scaling factor for the event amplitude parameters. 70 | % Parameters should be scaled so that their ranges are of similar magnitude. 71 | % This improves the performance of fmincon. 72 | % 73 | % boxampfact (1/10) = scaling factor for the box amplitude parameters. 74 | % 75 | % latfact (1/1000) = scaling factor for the latency parameters. 76 | % 77 | % tmaxfact (1/1000) = scaling factor for the tmax parameter. 78 | % 79 | % yintfact (1/10) = scaling factor for the yint parameter. 80 | % 81 | % slopefact (100) = scaling factor for the slope parameter. 82 | % 83 | % fmincon. 84 | % A, B, Aeq, Beq, NONLCON, options = input arguments/options 85 | % for fmincon. See documentation for fmincon for more details. 86 | % 87 | % pret_model_check = options for pret_model_check 88 | % 89 | % 90 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 91 | % 92 | % This program is free software: you can redistribute it and/or modify 93 | % it under the terms of the GNU General Public License as published by 94 | % the Free Software Foundation, either version 3 of the License, or 95 | % (at your option) any later version. 96 | % 97 | % This program is distributed in the hope that it will be useful, 98 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 99 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 100 | % GNU General Public License for more details. 101 | % 102 | % You should have received a copy of the GNU General Public License 103 | % along with this program. If not, see . 104 | % 105 | 106 | if nargin < 5 107 | opts = pret_default_options(); 108 | options = opts.pret_optim; 109 | clear opts 110 | if nargin < 1 111 | optim = options; 112 | return 113 | end 114 | end 115 | 116 | %OPTIONS 117 | optimplotflag = options.optimplotflag; 118 | pret_cost_options = options.pret_cost; 119 | pret_model_check_options = options.pret_model_check; 120 | 121 | %Factors to scale parameters by so that they are of a similar magnitude. 122 | %Improves performance of the constrained optimization algorithm (fmincon) 123 | ampfact = options.ampfact; 124 | boxampfact = options.boxampfact; 125 | latfact = options.latfact; 126 | tmaxfact = options.tmaxfact; 127 | yintfact = options.yintfact; 128 | slopefact = options.slopefact; 129 | 130 | %input values/options for fmincon 131 | A = options.fmincon.A; 132 | B = options.fmincon.B; 133 | Aeq = options.fmincon.Aeq; 134 | Beq = options.fmincon.Beq; 135 | NONLCON = options.fmincon.NONLCON; 136 | fmincon_options = options.fmincon.options; 137 | 138 | sfact = samplerate/1000; 139 | time = trialwindow(1):1/sfact:trialwindow(2); 140 | 141 | %check inputs 142 | pret_model_check(model,pret_model_check_options) 143 | 144 | %data is a vector 145 | % if size(data,1) ~= 1 146 | % error('The "data" argument must be a row vector') 147 | % end 148 | 149 | %samplerate, trialwindow vs data 150 | if length(time) ~= size(data,2) 151 | error('The number of time points according to samplerate and trialwindow does not equal the number of data points in data') 152 | end 153 | 154 | %sample rate vs model sample rate 155 | if samplerate ~= model.samplerate 156 | error('The input sample rate and the sample rate in model do not match') 157 | end 158 | 159 | %model time window vs time points 160 | if ~(any(model.window(1) == time)) || ~(any(model.window(2) == time )) 161 | error('Model time window does not fall on time points according to sample rate and trial window') 162 | end 163 | 164 | %crop data to match model.window 165 | datalb = find(model.window(1) == time); 166 | dataub = find(model.window(2) == time); 167 | data = data(:,datalb:dataub); 168 | 169 | %recalculate time point vector 170 | time = model.window(1):1/sfact:model.window(2); 171 | 172 | %construct inputs into fmincon (X, bounds, etc) 173 | X=[]; lb=[]; ub=[]; numparams = 0; 174 | 175 | if model.ampflag 176 | X = model.ampvals .* ampfact; 177 | lb = model.ampbounds(1,:) .* ampfact; 178 | ub = model.ampbounds(2,:) .* ampfact; 179 | numparams = numparams + length(model.ampvals); 180 | end 181 | 182 | if model.boxampflag 183 | X = [X model.boxampvals.*boxampfact]; 184 | lb = [lb model.boxampbounds(1,:).*boxampfact]; 185 | ub = [ub model.boxampbounds(2,:).*boxampfact]; 186 | numparams = numparams + length(model.boxampvals); 187 | end 188 | 189 | if model.latflag 190 | X = [X model.latvals.*latfact]; 191 | lb = [lb model.latbounds(1,:).*latfact]; 192 | ub = [ub model.latbounds(2,:).*latfact]; 193 | numparams = numparams + length(model.latvals); 194 | end 195 | 196 | if model.tmaxflag 197 | X = [X model.tmaxval.*tmaxfact]; 198 | lb = [lb model.tmaxbounds(1).*tmaxfact]; 199 | ub = [ub model.tmaxbounds(2).*tmaxfact]; 200 | numparams = numparams + length(model.tmaxval); 201 | end 202 | 203 | if model.yintflag 204 | X = [X model.yintval.*yintfact]; 205 | lb = [lb model.yintbounds(1).*yintfact]; 206 | ub = [ub model.yintbounds(2).*yintfact]; 207 | numparams = numparams + length(model.yintval); 208 | end 209 | 210 | if model.slopeflag 211 | X = [X model.slopeval.*slopefact]; 212 | lb = [lb model.slopebounds(1).*slopefact]; 213 | ub = [ub model.slopebounds(2).*slopefact]; 214 | numparams = numparams + length(model.slopeval); 215 | end 216 | 217 | % define cost function 218 | f = @(X)optim_cost(X,data,model); 219 | 220 | if optimplotflag 221 | fmincon_options.OutputFcn = @fmincon_outfun; 222 | end 223 | 224 | % define variables used during optimization 225 | SSt = nansum((data(:)-nanmean(data(:))).^2); % for R2 calculation 226 | 227 | % do the optimization 228 | [X, cost] = fmincon(f,X,A,B,Aeq,Beq,lb,ub,NONLCON,fmincon_options); 229 | 230 | % organize optimization results 231 | model = unloadX(X,model); 232 | optim = struct('eventtimes',model.eventtimes,'boxtimes',{model.boxtimes},... 233 | 'samplerate',model.samplerate,'window',model.window,... 234 | 'ampvals',model.ampvals,'boxampvals',model.boxampvals,... 235 | 'latvals',model.latvals,'tmaxval',model.tmaxval,... 236 | 'yintval',model.yintval,'slopeval',model.slopeval); 237 | optim.numparams = numparams; 238 | optim.cost = cost; 239 | optim.R2 = 1 - (cost/SSt); 240 | n = nnz(~isnan(data(:))); 241 | optim.BICrel = (n * log(cost/n)) + (numparams *log(n)); 242 | 243 | function cost = optim_cost(X,data,model) 244 | model = unloadX(X,model); 245 | cost = pret_cost(data,samplerate,model.window,model,pret_cost_options); 246 | end 247 | 248 | function stop = fmincon_outfun(X,optimValues,state) 249 | stop = false; 250 | switch state 251 | case 'iter' 252 | clf 253 | model = unloadX(X,model); 254 | gobj1 = plot(time,data','k','LineWidth',1.5); 255 | [~, gobj2] = pret_plot_model(model); 256 | legend([gobj1(1) gobj2],{'data' 'model'}) 257 | yl = ylim; 258 | xl = xlim; 259 | R2 = 1 - (optimValues.fval/SSt); 260 | text((xl(2)-xl(1))*.1+xl(1),(yl(2)-yl(1))*.95+yl(1),['Evals: ' num2str(optimValues.funccount)],'HorizontalAlignment','center','BackgroundColor',[0.7 0.7 0.7]); 261 | text((xl(2)-xl(1))*.1+xl(1),(yl(2)-yl(1))*.88+yl(1),['R^2: ' num2str(R2)],'HorizontalAlignment','center','BackgroundColor',[0.7 0.7 0.7]); 262 | pause(0.04) 263 | case 'interrupt' 264 | % No actions here 265 | case 'init' 266 | fh = figure(1); 267 | case 'done' 268 | try 269 | close(fh); 270 | catch 271 | end 272 | otherwise 273 | end 274 | end 275 | 276 | % reads out parameter values from X and places them into model 277 | % structure. rescales values to original units. if fitted latencies 278 | % have resulted in a change in event order, reorders events to ensure 279 | % they occur in a serial order. 280 | function model = unloadX(X,model) 281 | numevents = length(model.eventtimes); 282 | numboxes = length(model.boxtimes); 283 | 284 | if model.ampflag 285 | numEA = numevents; 286 | model.ampvals = X(1:numEA) .* (1/ampfact); 287 | else 288 | numEA = 0; 289 | end 290 | if model.boxampflag 291 | numBA = numboxes; 292 | model.boxampvals = X(numEA+1:numEA+numBA) .* (1/boxampfact); 293 | else 294 | numBA = 0; 295 | end 296 | if model.latflag 297 | numL = numevents; 298 | Btemp = model.ampvals; 299 | Ltemp = X(numEA+numBA+1:numEA+numBA+numL).*(1/latfact); 300 | %sort component pupil responses by the order they occur in on 301 | %the basis of eventtime + latency 302 | %ensures that sequential pupil responses are ascribed to the 303 | %proper event by assuming the event-related responses occur in 304 | %the same order as the events occur 305 | times = model.eventtimes + Ltemp; 306 | [timessort,ind] = sort(times); 307 | Btemp = Btemp(ind); 308 | model.latvals = timessort - model.eventtimes; 309 | model.ampvals = Btemp; 310 | else 311 | numL = 0; 312 | end 313 | if model.tmaxflag 314 | numt = 1; 315 | model.tmaxval = X(numEA+numBA+numL+1) .* (1/tmaxfact); 316 | else 317 | numt = 0; 318 | end 319 | if model.yintflag 320 | numy = 1; 321 | model.yintval = X(numEA+numBA+numL+numt+1) .* (1/yintfact); 322 | else 323 | numy = 0; 324 | end 325 | if model.slopeflag 326 | model.slopeval = X(numEA+numBA+numL+numt+numy+1) .* (1/slopefact); 327 | end 328 | end 329 | 330 | end 331 | -------------------------------------------------------------------------------- /pret_plot_boots.m: -------------------------------------------------------------------------------- 1 | function fhs = pret_plot_boots(boots,model,options) 2 | % pret_plot_boots(boots,model) 3 | % fhs = pret_plot_boots(boots,model) 4 | % fhs = pret_plot_boots(boots,model,options) 5 | % options = pret_plot_boots() 6 | % 7 | % Plots box and whisker plots of each parameter that was estimated as part 8 | % of the bootstrapping procedure. Box shows median and 50% confidence 9 | % interval, whiskers show 95% confidence interval, and remaining values are 10 | % plotted as single points. Generates one figure per parameter type. 11 | % 12 | % NOTE: Will close all figures currently open. 13 | % 14 | % Inputs: 15 | % 16 | % boots = boots structure output by pret_bootstrap containing the 17 | % parameter estimates for each bootstrap iteration. 18 | % 19 | % model = model structure created by pa_model and filled in by user. 20 | % Parameter values in model.ampvals, model.boxampvals, model.latvals, 21 | % model.tmaxval, model.yintval, and model.slopeval do NOT need to be 22 | % provided. 23 | % 24 | % Outputs: 25 | % 26 | % fhs = figure array containing handles for every plot generated. 27 | % 28 | % Options: 29 | % 30 | % pret_model_check = options for pret_model_check 31 | % 32 | % 33 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 34 | % 35 | % This program is free software: you can redistribute it and/or modify 36 | % it under the terms of the GNU General Public License as published by 37 | % the Free Software Foundation, either version 3 of the License, or 38 | % (at your option) any later version. 39 | % 40 | % This program is distributed in the hope that it will be useful, 41 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 42 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 43 | % GNU General Public License for more details. 44 | % 45 | % You should have received a copy of the GNU General Public License 46 | % along with this program. If not, see . 47 | % 48 | 49 | close all 50 | 51 | if nargin < 3 52 | opts = pret_default_options(); 53 | options = opts.pret_plot_boots; 54 | clear opts 55 | if nargin < 1 56 | fhs = options; 57 | return 58 | end 59 | end 60 | 61 | %OPTIONS 62 | pret_model_check_options = options.pret_model_check; 63 | 64 | %check input 65 | pret_model_check(model,pret_model_check_options) 66 | 67 | %boots vs model 68 | if any(boots.eventtimes ~= model.eventtimes) || any(boots.window ~= model.window) || boots.samplerate ~= model.samplerate || length(boots.boxtimes) ~= length(model.boxtimes) 69 | warning('Information in "boots" does not seem to match information in "model", check inputs\n') 70 | end 71 | 72 | for bx = 1:length(boots.boxtimes) 73 | if any(boots.boxtimes{bx} ~= model.boxtimes{bx}) 74 | warning('Information in "boots" does not seem to match information in "model", check inputs\n') 75 | end 76 | end 77 | 78 | fhs = gobjects(0); 79 | 80 | if length(model.eventtimes) ~= length(model.eventlabels) 81 | fprintf('Number of event labels not equal to number of events.\nUsing generic labels instead\n') 82 | model.eventtimes = num2cell(1:length(model.eventtimes)); 83 | end 84 | 85 | if length(model.boxtimes) ~= length(model.boxlabels) 86 | fprintf('Number of box labels not equal to number of boxes.\nUsing generic labels instead\n') 87 | model.boxtimes = num2cell(1:length(model.boxtimes)); 88 | end 89 | 90 | if model.ampflag 91 | 92 | %amplitude plot 93 | fhs = [fhs figure]; 94 | set(gcf,'Position',[100 100 560 420]) 95 | hold on 96 | xlim([0 length(boots.eventtimes)+1]) 97 | for ev = 1:length(boots.eventtimes) 98 | boxwhisker(boots.ampvals(:,ev),ev) 99 | end 100 | set(gca,'FontSize',12) 101 | set(gca,'XTick',1:length(boots.eventtimes)) 102 | set(gca,'XTickLabel',model.eventlabels) 103 | set(gca,'XTickLabelRotation',45) 104 | xlabel('Event','FontSize',16) 105 | ylabel('Amplitude (% change from baseline)','FontSize',16) 106 | title('Event Amplitude Bootstrap Estimates','FontSize',16) 107 | 108 | end 109 | 110 | if model.latflag 111 | 112 | %latency plot 113 | fhs = [fhs figure]; 114 | set(gcf,'Position',[200 100 560 420]) 115 | hold on 116 | xlim([0 length(boots.eventtimes)+1]) 117 | for ev = 1:length(boots.eventtimes) 118 | boxwhisker(boots.latvals(:,ev),ev) 119 | end 120 | set(gca,'FontSize',12) 121 | set(gca,'XTick',1:length(boots.eventtimes)) 122 | set(gca,'XTickLabel',model.eventlabels) 123 | set(gca,'XTickLabelRotation',45) 124 | xlabel('Event','FontSize',16) 125 | ylabel('Latency (ms)','FontSize',16) 126 | title('Event Latency Bootstrap Estimates','FontSize',16) 127 | end 128 | 129 | if model.boxampflag 130 | 131 | %box amplitude plot 132 | fhs = [fhs figure]; 133 | set(gcf,'Position',[300 100 560 420]) 134 | hold on 135 | xlim([0 length(boots.boxtimes)+1]) 136 | for bx = 1:length(boots.boxtimes) 137 | boxwhisker(boots.boxampvals(:,bx),bx) 138 | end 139 | set(gca,'FontSize',12) 140 | set(gca,'XTick',1:length(boots.boxtimes)) 141 | set(gca,'XTickLabel',model.boxlabels) 142 | set(gca,'XTickLabelRotation',45) 143 | xlabel('Event','FontSize',16) 144 | ylabel('Amplitude (% change from baseline)','FontSize',16) 145 | title('Box Amplitude Bootstrap Estimates','FontSize',16) 146 | 147 | end 148 | 149 | if model.tmaxflag 150 | 151 | %tmax plot 152 | fhs = [fhs figure]; 153 | set(gcf,'Position',[400 100 560 420]) 154 | hold on 155 | xlim([0 2]) 156 | boxwhisker(boots.tmaxvals,1) 157 | set(gca,'FontSize',12) 158 | set(gca,'XTick',1) 159 | set(gca,'XTickLabel',{'t_{max}'}) 160 | ylabel('Time (ms)','FontSize',16) 161 | title('t_{max} Bootstrap Estimates','FontSize',16) 162 | 163 | end 164 | 165 | if model.yintflag 166 | 167 | %y-intercept plot 168 | fhs = [fhs figure]; 169 | set(gcf,'Position',[500 100 560 420]) 170 | hold on 171 | xlim([0 2]) 172 | boxwhisker(boots.yintvals,1) 173 | set(gca,'FontSize',12) 174 | set(gca,'XTick',1) 175 | set(gca,'XTickLabel',{'y-intercept'}) 176 | ylabel('Amplitude (% change from baseline)','FontSize',16) 177 | title('y-intercept Bootstrap Estimates','FontSize',16) 178 | 179 | end 180 | 181 | if model.slopeflag 182 | 183 | %y-intercept plot 184 | fhs = [fhs figure]; 185 | set(gcf,'Position',[600 100 560 420]) 186 | hold on 187 | xlim([0 2]) 188 | boxwhisker(boots.slopevals,1) 189 | set(gca,'FontSize',12) 190 | set(gca,'XTick',1) 191 | set(gca,'XTickLabel',{'slope'}) 192 | ylabel('Amplitude/time (% change/ms)','FontSize',16) 193 | title('slope Bootstrap Estimates','FontSize',16) 194 | 195 | end 196 | 197 | 198 | function boxwhisker(x,g) 199 | % draws single vertical box and whsiker plot from distribution x at x axis 200 | % value of g with median, 50% CI box, and 95% CI whiskers. 201 | w = 0.5; 202 | lw = 1.5; 203 | 204 | xmed = nanmedian(x); 205 | x25 = prctile(x,25); 206 | x75 = prctile(x,75); 207 | x2p5 = prctile(x,2.5); 208 | x97p5 = prctile(x,97.5); 209 | xout = x(x < x2p5 | x > x97p5); 210 | 211 | hold on 212 | 213 | plot([g g],[x75 x97p5],'--k','LineWidth',lw) 214 | plot([g-w/2 g+w/2],[x97p5 x97p5],'k','LineWidth',lw) 215 | 216 | plot([g g],[x2p5 x25],'--k','LineWidth',lw) 217 | plot([g-w/2 g+w/2],[x2p5 x2p5],'k','LineWidth',lw) 218 | 219 | plot([g-w/2 g+w/2],[xmed xmed],'r','LineWidth',lw) 220 | 221 | plot([g-w/2 g+w/2],[x25 x25],'b','LineWidth',lw) 222 | plot([g-w/2 g+w/2],[x75 x75],'b','LineWidth',lw) 223 | plot([g-w/2 g-w/2],[x25 x75],'b','LineWidth',lw) 224 | plot([g+w/2 g+w/2],[x25 x75],'b','LineWidth',lw) 225 | 226 | plot(g*ones(1,length(xout)),xout,'+k','MarkerSize',6) 227 | end 228 | 229 | end -------------------------------------------------------------------------------- /pret_plot_model.m: -------------------------------------------------------------------------------- 1 | function [fh, go] = pret_plot_model(model,options) 2 | % pret_plot_model(model) 3 | % pret_plot_model(model,options) 4 | % 5 | % Plots the time series and its constituent regresssors resulting from the 6 | % model parameters and specifications in the given model structure. 7 | % 8 | % Inputs: 9 | % 10 | % model = model structure created by pret_model and filled in by user. 11 | % Parameter values in model.ampvals, model.boxampvals, model.latvals, 12 | % model.tmaxval, model.yintval, and model.slopeval must be provided. 13 | % *Note - an estim/optim structure from pret_estimate, pret_bootstrap, or 14 | % pret_optim can be input in the place of model* 15 | % 16 | % options = options structure for pret_calc. Default options can be 17 | % returned by calling this function with no arguments. 18 | % 19 | % Outputs: 20 | % 21 | % fh = figure handle for the resulting figure. 22 | % 23 | % Options: 24 | % 25 | % pret_calc_options = options structure for pret_calc, which 26 | % pret_plot_model uses to calculate the model time series and 27 | % regressors that are plotted. 28 | % 29 | % 30 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 31 | % 32 | % This program is free software: you can redistribute it and/or modify 33 | % it under the terms of the GNU General Public License as published by 34 | % the Free Software Foundation, either version 3 of the License, or 35 | % (at your option) any later version. 36 | % 37 | % This program is distributed in the hope that it will be useful, 38 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 39 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 40 | % GNU General Public License for more details. 41 | % 42 | % You should have received a copy of the GNU General Public License 43 | % along with this program. If not, see . 44 | % 45 | 46 | if nargin < 2 47 | opts = pret_default_options(); 48 | options = opts.pret_plot_model; 49 | clear opts 50 | if nargin < 1 51 | fh = options; 52 | return 53 | end 54 | end 55 | 56 | %OPTIONS 57 | pret_calc_options = options.pret_calc; 58 | 59 | sfact = model.samplerate/1000; 60 | time = model.window(1):1/sfact:model.window(2); 61 | 62 | [Ycalc, X] = pret_calc(model,pret_calc_options); 63 | 64 | X = X + model.yintval; 65 | xl = model.window; 66 | onsets = model.eventtimes + model.latvals; 67 | if any(onsets < model.window(1)) 68 | xl(1) = min(onsets(onsets < model.window(1))) - diff(model.window)/10; 69 | end 70 | if any(onsets > model.window(2)) 71 | xl(2) = max(onsets(onsets > model.window(2))) + diff(model.window)/10; 72 | end 73 | 74 | fh = gcf; 75 | hold on 76 | plot(time,model.slopeval*time + model.yintval,'k','LineWidth',1); 77 | go = plot(time,Ycalc,'--','color',[0.7 0.7 0.7],'LineWidth',1.5); 78 | ax = gca; 79 | ax.ColorOrderIndex = 1; 80 | plot(time,X,'LineWidth',1.5) 81 | plot([model.window(1) model.window(2)],[model.yintval model.yintval],'k','LineWidth',1); 82 | xlim(xl) 83 | yl = ylim; 84 | ax.ColorOrderIndex = 1; 85 | plot(repmat(onsets,2,1),repmat([yl(1) ; yl(2)],1,length(model.eventtimes)),'--','LineWidth',1); 86 | ax.FontSize = 12; 87 | xlabel('Time (ms)','FontSize',16) 88 | ylabel('Pupil area (percent change)','FontSize',16) 89 | legend([go],{'model'}) 90 | -------------------------------------------------------------------------------- /pret_preprocess.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobaparker/PRET/c2769ece8513dfbebd3067a90f11f5038b4aebf8/pret_preprocess.m -------------------------------------------------------------------------------- /pret_sample_script.m: -------------------------------------------------------------------------------- 1 | %% pret_sample_script 2 | % 3 | % A script demonstrating several functionalities of PRET. It can also be 4 | % used to test if PRET is working properly (it will test almost every 5 | % function in the toolbox). 6 | % 7 | % If you are working through the script to learn how to use PRET, I suggest 8 | % you run each section one by one. If you are testing your installation of 9 | % PRET, just run the entire thing. 10 | % 11 | % For more information about the modeling framework presented here, see 12 | % [insert citation here] 13 | % 14 | % 15 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 16 | % 17 | % This program is free software: you can redistribute it and/or modify 18 | % it under the terms of the GNU General Public License as published by 19 | % the Free Software Foundation, either version 3 of the License, or 20 | % (at your option) any later version. 21 | % 22 | % This program is distributed in the hope that it will be useful, 23 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 | % GNU General Public License for more details. 26 | % 27 | % You should have received a copy of the GNU General Public License 28 | % along with this program. If not, see . 29 | % 30 | 31 | %% Define a task (by creating a model) 32 | % Let's preallocate an empty model structure to represent a hypothetical 33 | % task. We are going to use this model to generate artificial data for a 34 | % single "subject". 35 | taskmodel = pret_model(); 36 | 37 | % Let's assume that we have a sampling frequency of 1000 Hz and that the 38 | % trials will be epoched from -500 to 3500 ms. 39 | taskmodel.window = [-500 3500]; 40 | taskmodel.samplerate = 1000; 41 | 42 | % Now let's suppose the trial sequence of this task is the following: 43 | % 44 | % A precue informs the observer that the trial has begun, the observer 45 | % sees 2 stimuli 1000 and 1250 ms after the 46 | % precue, then a postcue 500 ms after the last stimulus instructs the 47 | % observer to respond. 48 | % 49 | % From this, we have 4 events; precue, stimulus 1, stimulus 2, postcue. 50 | % Let's say the precue is time 0 ms, then these events occur at 0 ms, 1000 51 | % ms, 1250 ms, and 1750 ms respectively. 52 | taskmodel.eventtimes = [0 1000 1250 1750]; 53 | taskmodel.eventlabels = {'precue' 'stim1' 'stim2' 'postcue'}; %optional 54 | 55 | % We also have a response at the end of the trial. For the sake of 56 | % simplicity, let's say the observer always responds 1000 ms after the 57 | % postcue. Let's assume that there's a constant internal signal 58 | % due to the cognitive workload associated with completing the task 59 | % and making the decision. This constant internal signal would start at 60 | % precue onset (0 ms) and last until time of response (2750 ms). In the 61 | % context of this model, this would be a box regressor. 62 | taskmodel.boxtimes = {[0 2750]}; 63 | taskmodel.boxlabels = {'task'}; %optional 64 | 65 | % Now we need to define the structure of the data using the model 66 | % parameters. Let's say that the amplitude and latency of event-related 67 | % pupil responses are expected to vary. 68 | taskmodel.ampflag = true; 69 | taskmodel.latflag = true; 70 | 71 | % Let's also say we expect amplitude of the task-related pupil response and 72 | % the tmax of the observer's pupil reponse to vary. 73 | taskmodel.boxampflag = true; 74 | taskmodel.tmaxflag = true; 75 | 76 | % Now let's assume the baseline is perfectly stable (the y-intercept would 77 | % always be 0) and that we don't expect any linear drift in pupil size 78 | % during the trial (slope = 0). 79 | taskmodel.yintflag = false; 80 | taskmodel.slopeflag = false; 81 | 82 | % Since the y-intercept and slope are always going to be 0, let's put that 83 | % information into the structure. 84 | taskmodel.yintval = 0; 85 | taskmodel.slopeval = 0; 86 | 87 | % Let's define boundaries for the parameters we will be fitting. Let's say 88 | % event and box-related amplitudes will all be between 0 and 100 (percent 89 | % signal change from baseline), event latencies will be between -500 and 90 | % 500 ms, and tmax will be between 500 and 1500. 91 | taskmodel.ampbounds = repmat([0;100],1,length(taskmodel.eventtimes)); 92 | taskmodel.latbounds = repmat([-500;500],1,length(taskmodel.eventtimes)); 93 | taskmodel.boxampbounds = [0;100]; 94 | taskmodel.tmaxbounds = [500;1500]; 95 | 96 | % So far, the model's specifications are common to all task conditions. Now 97 | % let's define different models for each condition so that we can 98 | % generate artificial data for each one. This is to simulate data that you 99 | % might obtain in an experiment with three conditions. 100 | 101 | %% Generate data 102 | % Let's suppose our subject completed 3 different conditions. 103 | condition1model = taskmodel; 104 | condition2model = taskmodel; 105 | condition3model = taskmodel; 106 | 107 | % Now let's suppose that these conditions are associated with differences 108 | % in this subject's data. To do this, let's enter different parameter values 109 | % for each condition. 110 | condition1model.ampvals = [1 5 7 2]; 111 | condition1model.latvals = [-50 -180 60 0]; 112 | condition1model.boxampvals = [3]; 113 | condition1model.tmaxval = 930; 114 | 115 | condition2model.ampvals = [7 2 9 4]; 116 | condition2model.latvals = [120 -20 -110 90]; 117 | condition2model.boxampvals = [1]; 118 | condition2model.tmaxval = 1200; 119 | 120 | condition3model.ampvals = [8 3 4 5]; 121 | condition3model.latvals = [-190 -250 -20 -110]; 122 | condition3model.boxampvals = [6]; 123 | condition3model.tmaxval = 800; 124 | 125 | % Now let's generate the data. Let's make 200 trials for each condition and 126 | % set the parameter generation mode to 'normal' (this means the parameters 127 | % used to generate each trial will be normally distributed around the 128 | % values we defined above). 129 | numtrials = 200; 130 | parammode = 'normal'; 131 | 132 | condition1data = pret_fake_data(numtrials,parammode,taskmodel.samplerate,taskmodel.window,condition1model); 133 | condition2data = pret_fake_data(numtrials,parammode,taskmodel.samplerate,taskmodel.window,condition2model); 134 | condition3data = pret_fake_data(numtrials,parammode,taskmodel.samplerate,taskmodel.window,condition3model); 135 | 136 | %% visualize generated data 137 | sfact = taskmodel.samplerate/1000; 138 | time = taskmodel.window(1):1/sfact:taskmodel.window(2); 139 | 140 | close all 141 | 142 | figure(1) 143 | plot(time,condition1data) 144 | title('Condition 1') 145 | xlabel('time (ms)') 146 | ylabel('pupil size (% change from baseline') 147 | 148 | figure(2) 149 | plot(time,condition2data) 150 | title('Condition 2') 151 | xlabel('time (ms)') 152 | ylabel('pupil size (% change from baseline') 153 | 154 | figure(3) 155 | plot(time,condition3data) 156 | title('Condition 3') 157 | xlabel('time (ms)') 158 | ylabel('pupil size (proportion change from baseline') 159 | 160 | %% organize data via pret_preprocess 161 | % To fit pupil data you collected in an experiment, you would start here, 162 | % with preprocessing. The data should be epoched by trial in a matrix of 163 | % trials x time for each condition. pret_preprocess creates an sj 164 | % (subject) structure with the data and metadata in a set format. If 165 | % requested, it also performs baseline normalization and blink 166 | % interpolation on the trial data. 167 | 168 | % The artificial data is already baseline normalized and has no blinks, so 169 | % we return the options structure and turn off those features 170 | options = pret_preprocess(); 171 | options.normflag = false; 172 | options.blinkflag = false; 173 | 174 | % put the condition data matrices into a cell array 175 | data = {condition1data condition2data condition3data}; 176 | 177 | % labels for each condition 178 | condlabels = {'condition1' 'condition2' 'condition3'}; 179 | 180 | % other epoch info 181 | samplerate = taskmodel.samplerate; 182 | window = taskmodel.window; 183 | 184 | sj = pret_preprocess(data,samplerate,window,condlabels,[],options); 185 | 186 | %% create model for the data 187 | % Pretending that we are naive to the model that we used to create our 188 | % data, let's create a model to actually fit the data to. 189 | model = pret_model(); 190 | 191 | % While the trial window of our task is from -500 to 3500 ms, here we are 192 | % not interested in what's happening before 0. So 193 | % let's set the model window to fit only to the region betweeen 0 and 3500 194 | % ms (the cost function will only be evaluated along this interval). 195 | model.window = [0 3500]; 196 | 197 | % We already know the sampling frequency. 198 | model.samplerate = taskmodel.samplerate; 199 | 200 | % We also know the event times of our task. Let's also say that we think 201 | % there will be a sustained internal signal from precue onset to response 202 | % time (0 to 2750 ms). 203 | model.eventtimes = [0 1000 1250 1750]; 204 | model.eventlabels = {'precue' 'stim1' 'stim2' 'postcue'}; %optional 205 | model.boxtimes = {[0 2750]}; 206 | model.boxlabels = {'task'}; %optional 207 | 208 | % Let's say we want to fit a model with the following parameters: 209 | % event-related, amplitude, latency, task-related (box) amplitude, 210 | % and the tmax of the pupil response function. We turn the other parameters 211 | % off. 212 | model.yintflag = false; 213 | model.slopeflag = false; 214 | 215 | % Now let's define the bounds for the parameters we decided to fit. We do 216 | % not have to give values for the y-intercept and slope because we are not 217 | % fitting them. 218 | model.ampbounds = repmat([0;100],1,length(model.eventtimes)); 219 | model.latbounds = repmat([-500;500],1,length(model.eventtimes)); 220 | model.boxampbounds = [0;100]; 221 | model.tmaxbounds = [500;1500]; 222 | 223 | % We need to fill in the values for the y-intercept and slope since we will 224 | % not be fitting them as parameters. 225 | model.yintval = 0; 226 | model.slopeval = 0; 227 | 228 | %% estimate model parameters via pret_estimate_sj 229 | % Now let's perform the parameter estimation procedure on our subject data. 230 | % The mean of each condition will be fit independently. For illustration, 231 | % let's run only 3 optimizations using one cpu worker (for more 232 | % information, see the help files of pret_estimate and pret_estimate_sj). 233 | options = pret_estimate_sj(); 234 | options.pret_estimate.optimnum = 3; 235 | % if you want to try fiting the parameters using single trials instead of the mean, 236 | % use these lines (you'll want to turn off the optimization plots for this): 237 | % options.trialmode = 'single'; 238 | % options.pret_estimate.pret_optim.optimplotflag = false; 239 | wnum = 1; 240 | 241 | sj = pret_estimate_sj(sj,model,wnum,options); 242 | 243 | %% Compare model fits to condition means 244 | close all 245 | 246 | figure(1) 247 | gobj1 = plot(time,sj.means.condition1,'k','LineWidth',1.5); 248 | hold on 249 | [~, gobj2] = pret_plot_model(sj.estim.condition1); 250 | legend([gobj1 gobj2],{'data' 'model'}); 251 | 252 | figure(2) 253 | gobj1 = plot(time,sj.means.condition2,'k','LineWidth',1.5); 254 | hold on 255 | [~, gobj2] = pret_plot_model(sj.estim.condition2); 256 | legend([gobj1 gobj2],{'data' 'model'}); 257 | 258 | figure(3) 259 | gobj1 = plot(time,sj.means.condition3,'k','LineWidth',1.5); 260 | hold on 261 | [~, gobj2] = pret_plot_model(sj.estim.condition3); 262 | legend([gobj1 gobj2],{'data' 'model'}); 263 | 264 | %% perform bootstrapping procedure via pret_bootstrap_sj 265 | % Finally, let's perform the bootstrapping procedure on our subject data. 266 | % Each condition will be bootstrapped independently. Let's lower the number 267 | % of bootstrap iterations and the number of optimizations (in the 268 | % estimation procedure performed during each bootstrap iteration). For more 269 | % information, see the help files of pret_bootstrap and pret_bootstrap_sj. 270 | % 271 | % summary figures showing distribution of each parameter's bootstrap 272 | % estimations will appear automatically 273 | options = pret_bootstrap_sj(); 274 | options.pret_bootstrap.pret_estimate.optimnum = 3; 275 | wnum = 1; 276 | nboots = 5; 277 | 278 | sj = pret_bootstrap_sj(sj,model,nboots,wnum,options); 279 | 280 | %% condition 1 bootstrap results 281 | % These box and whisker plots show the median, quartiles, and 95% 282 | % confidence interval of each parameter (we only completed 5 bootstrap 283 | % iterations, so these plots only represent distributions of 5 points). 284 | close all 285 | pret_plot_boots(sj.boots.condition1,model); 286 | 287 | %% condition 2 bootstrap results 288 | close all 289 | pret_plot_boots(sj.boots.condition2,model); 290 | 291 | %% condition 3 bootstrap results 292 | close all 293 | pret_plot_boots(sj.boots.condition3,model); 294 | -------------------------------------------------------------------------------- /pupilrf.m: -------------------------------------------------------------------------------- 1 | function output = pupilrf(t,n,tmax,t0) 2 | % pupilrf 3 | % output = pupilrf(t,n,tmax,t0) 4 | % 5 | % Calculates the pupil response function at time points "t" with given 6 | % parameters n, tmax, and t0. 7 | % 8 | % INPUTS 9 | % t = time vector (in ms) 10 | % n+1 = number of layers (canonical value of n = 10.1) 11 | % tmax = response maximum (canonical value of tmax = 930) 12 | % function is normalized to a max of 0.01 (tmax affects amplitude of pupil 13 | % response function) 14 | % t0 = the time of the event 15 | % 16 | % OUTPUT 17 | % output = time series of pupil response function resulting from the input 18 | % parameters, at the time points specified in t 19 | % 20 | % For more information about the pupil response function, see "Pupillary 21 | % dilation as a measure of attention: A quantitative system analysis", 22 | % Hoeks&Levelt 1993 23 | % 24 | % 25 | % Copyright (C) 2019 Jacob Parker and Rachel Denison 26 | % 27 | % This program is free software: you can redistribute it and/or modify 28 | % it under the terms of the GNU General Public License as published by 29 | % the Free Software Foundation, either version 3 of the License, or 30 | % (at your option) any later version. 31 | % 32 | % This program is distributed in the hope that it will be useful, 33 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 34 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 35 | % GNU General Public License for more details. 36 | % 37 | % You should have received a copy of the GNU General Public License 38 | % along with this program. If not, see . 39 | % 40 | 41 | output = ((t-t0).^n).*exp(-n.*(t-t0)./tmax); 42 | output((t-t0)<=0) = 0; 43 | output = (output/(max(output))); 44 | --------------------------------------------------------------------------------