Changes in version 1.5.0.9000 Reproducibility impact - InfoVal reference distributions are now computed through the stimulus Gram matrix by default, and differ from before by rounding. reference_method = "images" reproduces earlier references bit for bit. computeInfoVal2IFC() and generateReferenceDistribution2IFC() gain a reference_method argument. The default, "gram", no longer renders each noise image and multiplies the noise for every simulated draw. The norms follow from the trials-by-trials Gram matrix of the saved noise, which sums in a different order. That matrix is built from the noise rendered through the sparse basis for a small set, and from the basis's cross-product, kept sparse, otherwise, one block of image columns at a time. "images" is the calculation of rcicr 1.5.0 and earlier, unchanged. (#354) The measured configurations are listed in analyses/gram-reference-accuracy.md: 64 to 512 pixels square, up to 770 stimuli, the reference BLAS. None gave a bit-identical reference. The largest relative difference in a norm was 3.3e-14, InfoVal moved by at most 4.8e-13, and no call at 1.96 changed among 2,800 classification images. For comparison, the standard deviation of InfoVal at 1.96 across 10,000-draw references, estimated for the one stimulus set it was measured on (512 pixels, 300 stimuli), was 0.031. Other BLAS libraries were not measured. Only a reference computed anew changes: for a new stimulus file, with force_gen_ref_dist = TRUE, with a response_seed, or when rcicr automatically rebuilds an unmarked reference that an older version left in the file. A reference already stored in the file is reused as stored, whichever method is asked for, so its InfoVal does not change. To reproduce an earlier InfoVal exactly after a reference has been rebuilt, rebuild it with reference_method = "images". Each newly stored reference records its method, as reference_norms_method or as method in the per-base and per-subset entries. Computing a reference with "gram" prints a message saying so, and naming "images" as the way to reproduce earlier references. The method is never switched for you. - generateStimuli2IFC() now leaves your random stream where it was. It seeds with its seed argument and used to leave the stream wherever stimulus generation ended. Now the next random number your script draws after the call is the one it would have drawn without it, and a session that had no .Random.seed before has none after. A script that draws random numbers after generating stimuli without calling set.seed() first therefore gets different draws. Nothing rcicr computes changes: stimuli, classification images, z-maps and InfoVal are all drawn from their own seeds. (#189) - InfoVal references for new stimulus files no longer depend on the scoring session's RNGkind(). generateStimuli2IFC() saves the session's RNGkind() in the .Rdata file as rng_kind, and reference responses, with or without a response_seed, are drawn under it. A file scored under the kind it was generated with gives the same reference as before. Scored under another kind, it used to give a different reference and now gives that same one. Stimuli are unchanged under every kind, and files written before this version still draw under the session's kind, with the message on an automatic refresh that says when values changed. (#315) In that cross-kind case, an explicit regeneration (force_gen_ref_dist = TRUE, a response_seed, or generateReferenceDistribution2IFC()) now leaves your random stream untouched, kind and position, instead of advancing it; advancing it would leave your session on the file's generator. Under the session's own kind it still advances the stream as before. - Pixels above white are written white, not dark, in stimulus and classification-image PNGs. png::writePNG() does not clip values above 1; it wraps them, so a pixel at 1.004 was written black and one at 1.1 almost black, while values below 0 were clipped. Every PNG rcicr writes is now clipped to [0, 1] first, which changes the files only at pixels that were above 1. No returned matrix, .Rdata value, CI or InfoVal changes. - Stimulus PNGs, the images participants saw, went above 1 where noise above 0.3 met a white base pixel (less on a near-white one). Out of 200 trials at 128 pixels per setting, 67% had such noise at nscales = 1, 12.5% at 2, 2% at 3 and 0.5% at 4. None was found at the default nscales = 5, in 20 trials each of sinusoid and Gabor noise at 512 pixels. Only those pixels on a bright part of the base wrapped. Stimuli already shown cannot be corrected; if you used fewer than 5 scales on a base with white areas, regenerating the stimuli with the same seed shows which pixels were affected. - Classification-image PNGs went above 1 under scaling = "constant" with a constant too small for the CI, whose warning already said "clipping will occur", and under scaling = "none" with rating weights large enough to push the noise past the base. On a mostly white 32-pixel base with scaling_constant = 0.01, 311 of 1024 pixels were above 1 and 248 of them were written darker than 0.5. (#371) - A masked classification image now gets an InfoVal instead of NA. generateCI(mask = ) stores NA in masked pixels, and computeInfoVal2IFC() and batchComputeInfoVal2IFC() returned NA for such a CI without saying why. The InfoVal is now computed over the unmasked pixels, against a reference built over the same pixels from the same stimuli and the same replayed response stream, so it is reproducible from the stimulus file. The mask is read from the CI's own NA pixels. Each mask's reference is simulated once and stored in the .Rdata file apart from the default one, and generateReferenceDistribution2IFC() gains a mask argument to store one in advance, for example with a response_seed for a file without a stimulus seed. An InfoVal over part of the image is not comparable with one over the whole image. Unmasked classification images are unaffected. (#374) - Logical, missing, infinite and non-numeric responses now stop generateCI() and the functions built on it. A script that passed logical responses got a classification image and now gets an error: TRUE/FALSE weighted every FALSE trial 0 rather than -1, so half the trials did not count. To keep exactly the old numbers, pass as.numeric(responses); for the CI a two-image choice means, pass ifelse(responses, 1, -1). The error says both. Missing values never gave a usable result: one NA response made every pixel NA (the PNG black), with only R's own "no non-missing arguments to min" warnings; factor and character responses did the same, and an infinite one made the CI NaN. The error names the trials. batchGenerateCI() and batchGenerateCI2IFC() check the whole response column first and name the groups affected. Finite numeric responses, including ratings, are unaffected. (#372) Performance and dependencies - Reference distributions are much faster and need less memory. At 10,000 iterations, generateReferenceDistribution2IFC() was about 19x faster at 128 pixels and 300 stimuli with the default ncores, and about 29x faster at 256 pixels with ncores = 2, measured by analyses/gram-reference-benchmark.R against the build before this change. At the 512-pixel, 770-stimulus default, the peak R memory of the session knitting analyses/gram-reference-accuracy.md was 1.7 GB while the Gram route ran, against 3.3 GB for the rendered route. ncores is now used only with reference_method = "images". (#354) - Rendering noise takes less time and each parallel worker less memory, with identical results. Every render now indexes the noise basis with integer patch indices; the .Rdata file keeps the basis as saved. With participants, each participant's rows go only to the worker computing that participant, and no loop starts more workers than it has participants or trials. On 512-pixel stimuli in analyses/worker-memory.md, generating stimuli, the t-test z-map and an "images" reference took 23% to 29% less time on one core and 13% to 24% less on four, and each worker took 0.8 to 1 GB instead of 0.9 to 1.2 GB. n_cores and ncores now document what a worker costs, and generateCI()'s that it also runs the per-participant CIs. (#405) - dplyr and yesno are no longer dependencies, and tibble is only suggested. All three served a table of precomputed InfoVal references that had been empty since 2018, so every lookup missed and every InfoVal was simulated; it is gone, and no InfoVal changes. The interactive prompt it could have shown never ran. (#392) - matlab and scales are no longer dependencies. Their calls in the noise basis and simulateNoiseIntensities() are base R now, written from the packages' own definitions. Every sinusoid and Gabor patch and every noise pattern is identical to before, including the 1-pixel patches of an img_size equal to 2^(nscales - 1). With dplyr and yesno gone too, and tibble only suggested, rcicr imports 10 packages where 1.5.0 imported 14, Matrix included. (#208) - Matrix, an R recommended package that rcicr already depended on through spatstat, is now a direct import. New features - Every classification image records how it was scaled, as a scaling attribute: the method applied and its constant, including the one 'independent' scaling computes from each CI, which was discarded before. With participants, it also records each participant's constant, so the individual CI images can be reproduced. autoscale(), and the batch functions with their default 'autoscale', record the shared constant they printed, and keep the earlier record for $combined, which they leave unchanged. No pixel changes; saveRDS() keeps the record with the result. (#9) - batchGenerateCI() and batchGenerateCI2IFC() gain a participants argument, the name of a column of participant IDs. Each unit's CI is then the average of its participants' CIs, as generateCI(participants = ) computes it, so with by naming a condition every participant counts equally however many trials they gave. Every row needs an ID; a missing one stops the call before anything is computed. Without the argument nothing changes: the two functions now share one implementation and return exactly what they did. (#87) - batchComputeInfoVal2IFC() computes the InfoVal of every classification image in a list, such as the result of batchGenerateCI2IFC(), and returns them as a named vector. The values are identical to those of computeInfoVal2IFC() called on each image in turn. reference_stimuli can be given per image, for example lapply(cis, function(ci) attr(ci, "trial_design")$stimuli). Each distinct reference is found or simulated once, and the stimulus file is read at most three times to find stored references, however many images there are, rather than twice per image. For 20 images on a 300-trial, 512-pixel stimulus file, that was about 13x faster than the loop with the reference already stored, and about 19x faster with a response_seed, where the loop simulates the reference again for every image (analyses/infoval-batch-cost.R). Trial-design messages are collected into one per kind, naming the images concerned. (#85) - computeInfoVal2IFC() and generateReferenceDistribution2IFC() can build the InfoVal reference over the stimuli a classification image was actually built from. Brinkman et al. (2019) require the reference to use the same stimuli as the CI, but the reference has always used every saved stimulus. A CI built from fewer, for example after dropping missed trials, therefore got an inflated InfoVal: at 512 pixels, pure-noise CIs had a median InfoVal of 0.09 to 0.11 with 1% of trials missing and 0.51 to 0.58 with 5%, in the stimulus sets measured in analyses/infoval-design-mismatch.md. Pass those stimuli as the new reference_stimuli argument, each once. The reference is reproducible from the stimulus file and stored in it, as reference_norms_by_stimuli, apart from the default reference. No default changes, so every existing call returns the same number. (#349) generateCI() now records which stimuli it used, whether any was repeated, and how many participants it averaged, as a trial_design attribute. computeInfoVal2IFC() reads it and prints a message when a CI is scored against a reference over different stimuli, giving the call that matches them: reference_stimuli = attr(ci, "trial_design")$stimuli. For a CI that averages repeated presentations or several participants, the message says instead that no reference is defined for that design, and suggests an InfoVal for each participant's CI. The message never changes the number returned. A CI from an older version has no attribute and gets no message. The attribute leaves the list's fields unchanged. - checkStimulusPNGs2IFC() checks a stimulus .Rdata file against stimulus PNGs, trial by trial, without the base images. It returns the share of each trial's pixels whose difference between the original and inverted stimulus agrees with the file's noise, for every base label, and lists PNGs the file does not cover. Use it to confirm the settings of a file regenerated after the original was lost; vignette("recipes") shows how. In the configurations measured in analyses/stimulus-png-residuals.md, a nearby Gabor sigma agreed on up to 99.6% of a trial's pixels, so compare candidates rather than reading a share alone. Bug fixes - checkStimulusPNGs2IFC() recognizes the byte wrap in older stimulus PNGs. Before 1.6.0, an original or inverted stimulus above white was encoded by png::writePNG() as an 8-bit byte that wrapped into the image, while the checker treated that decoded pixel as an ordinary interior value. It could therefore report a low agreement share for the correct saved noise. The check now accepts the direct difference or the one 256/255 difference made by that historical wrap, only when the decoded pair is dark enough to permit it. Pixels decoded as exactly black or white remain excluded, and a matching share is still evidence to assess across all trials and against other candidate settings; the images participants saw cannot be changed by this check. Current clamped PNGs and saved stimulus data are unchanged. (#419) - generateStimuli2IFC() never overwrites stimulus PNGs. PNG names carry no time, so a call in a later minute into the same folder, with the same label, base label and seed, overwrote the earlier PNGs. The earlier .Rdata survived and no longer described them, and a shorter rerun left old and new PNGs mixed. Now every PNG the call would write is reserved before anything is generated. If any exists, the call stops with nothing changed. It also stops when two base labels name the same file on this file system, such as face and Face where case is ignored, and while another same-seed call is writing PNGs into the folder. A call that fails part-way, or that you abort with Esc or Ctrl-C, first stops its parallel workers and then removes the files it created. Only if R itself is killed can empty placeholder PNGs remain; the next call's message says the files it found are empty, as such placeholders would be, so they can be checked and deleted. To regenerate a stimulus set on purpose, delete its PNGs and its .Rdata first, or use a different label or stimulus_path. (#350) - generateCI(zmap = TRUE) no longer stops with color intensity ... not in [0,1] under scaling = "none" or an out-of-range "constant". The z-map is drawn over the combined image, which then leaves [0, 1]: under "none", wherever the base is black and the CI negative, and contrast maximization always makes one base pixel black. The background is now clipped to [0, 1] where it is drawn, in plotZmap() too. (#373) - autoscale() scales every element of its list, whatever the names. It looked elements up by name, so with a name used twice, as c() of two batch results with the same participant IDs gives, only the first was read and scaled: the shared constant ignored the others, and they were returned without $scaled. An unnamed list failed with "dimensions must be a positive quantity". Lists with unique names get the same constant and output as before. With save_as_pngs = TRUE, where names become file names, a missing or repeated name now stops the call before anything is written. (#375) - An unrecognised noise_type now stops generateStimuli2IFC() and generateNoisePattern(). Anything but exactly "sinusoid" or "gabor" used to produce sinusoid noise and record the value as given, so noise_type = "Gabor" wrote sinusoid stimuli into a file that said Gabor. The check runs before anything is written. Every value still accepted produces the same noise as before. (#376) - Every InfoVal reference path makes the same checks. generateReferenceDistribution2IFC() checks iter before anything is loaded or simulated: on a file whose base images share their parameters, iter = 2.5 used to run 2 iterations, and 0 or NA failed inside a progress bar. A reference with a MAD of 0 now stops computeInfoVal2IFC() on every path, where only a reference_stimuli reference did before and the others returned an infinite or NaN InfoVal; a stored default reference holding NA or infinite values stops as stored per-base and subset references already did. Every finite InfoVal is unchanged. (#377) - generateStimuli2IFC() checks n_trials before writing anything. 0, negative and missing values failed with messages such as "dimensions must be a positive quantity", depending on save_as_png. A fractional one, such as 2.5, wrote 2 trials into a stimulus file whose n_trials every InfoVal then rejected. All now stop with "n_trials must be a positive whole number". (#385) - An unknown zmapmethod now says so. generateCI(zmap = TRUE) with a method other than "quick" or "t.test" stopped with "argument of length 0" from inside the plotting code; it now names the two methods. (#388) - A single generated trial or a single participant no longer stops with must have 'max' > 'min'. generateStimuli2IFC(n_trials = 1), and generateCI() with one participant ID, now run: the progress bars start at 0. So does one participant with one trial. Where each stimulus appears once, one participant's CI is the same as the pooled CI of their trials. With repeated presentations the two weight trials differently, as ?generateCI describes. A t.test z-map over fewer than two images, one participant or one distinct stimulus, cannot be computed. It now stops before any work with a message that says so and suggests zmapmethod = "quick", instead of failing inside t.test(). No result that was computed before changes. (#351) Documentation - A new vignette, vignette("recipes"), answers questions that come up after the basics. It covers noise-only stimuli from a uniform grey base image, which needs maximize_baseimage_contrast = FALSE; rating scales as response weights, and how to check their direction; and matching the InfoVal reference to the design: dropped trials (reference_stimuli), masked classification images, scoring a batch, and why a CI averaged over participants has no calibrated InfoVal. It runs at 64 pixels and builds in seconds. (#69, #6) - A new vignette, vignette("stored-data"), lists every field of the stimulus .Rdata file and every element and attribute of the classification image generateCI() returns. It replaces the README's table of the file, which lacked reference_norms_method and the method and mask fields of reference entries. The vignette's build checks its tables against freshly generated files and classification images, so they always describe the installed version. ?generateCI and ?autoscale now point there for the fields of the trial_design and scaling attributes. - vignette("recipes") has a recipe for reproducing a number from an earlier version: running that release from a library of its own, or recomputing with this one. It says which stored InfoVal references are reused and which are rebuilt, shows that reference_method = "images" rebuilds one with the calculation of 1.5.0 and earlier, and points to the individual-CI filename advisory for per-participant images from GitHub installs before 1.3.0. - vignette("recipes") shows how to regenerate a lost .Rdata file from the seed and the generation settings, and how to check the result against the stimulus PNGs without the base images. The README, the walkthrough and vignette("stored-data") no longer say the file cannot be recovered. - The help pages of generateStimuli2IFC(), generateCI(), autoscale(), computeInfoVal2IFC() and the other functions the vignettes use link to the vignette sections that show them, and ?rcicr names all the vignettes. - The README says how to cite rcicr and the method behind it, and lists all four vignettes. Changes in version 1.5.0 (2026-09-27) Reproducibility impact - batchGenerateCI() and batchGenerateCI2IFC() now group a tibble correctly. Called on a tibble with more than one group in by, rcicr 1.1.0 through 1.4.1 returned a single classification image instead of one per group. That image was built from alternating trials of different groups and named after the first group. A data.frame, or a tibble with one group, was never affected, and its results do not change. (#336) You were affected if the list these functions returned had one element although your data held several participants or conditions; data read with readr or processed with dplyr is a tibble. Recompute those CIs with this version, or with an older one after as.data.frame(). No stimuli need regenerating or responses recollecting. Of the affected versions, only 1.3.0 and 1.4.1 were released on CRAN; the others were GitHub releases. The 1.1.0 entry below saying these functions accept tibble columns held only for a single group. - The default InfoVal reference now needs the stimulus seed saved in the file. Without a response_seed, the reference replays that seed. A file without one (made with generateStimuli2IFC(seed = NULL), or with the seed field removed) now stops with an error before anything is simulated. The error gives the call that stores a reproducible reference, generateReferenceDistribution2IFC(rdata, response_seed = ), which later computeInfoVal2IFC() calls reuse, and, for a file that cannot be written, computeInfoVal2IFC(..., response_seed = ). Files with a seed are unaffected. Such a file used to reseed from the clock, so its reference could not be reproduced. generateReferenceDistribution2IFC() and computeInfoVal2IFC(force_gen_ref_dist = TRUE) returned a different one on every call. The first automatic computeInfoVal2IFC() stored one that later calls reused, so the InfoVal was stable on that copy of the file and different on any other. This happened on every version back to 1.0.1 for files made with seed = NULL, and on 1.4.0 and 1.4.1 for files without the field; earlier versions stopped on those. A reference already stored by 1.4.0 or later is still used, with a message that it cannot be regenerated from the file. An older stored reference, which rcicr rebuilds, now stops like any other. Replacing either with a seeded reference makes the InfoVal reproducible; it will differ from the one reported before. (#334) Bug fixes - Saving a reference distribution no longer risks the stimulus .Rdata file. computeInfoVal2IFC() and generateReferenceDistribution2IFC() store the reference in that file, and a save that was interrupted (Esc, an error, R killed or crashing) could leave it unloadable, losing the only record of the stimuli. The file is now backed up beside itself for the duration of the save, as .rcicr-backup, and restored automatically if the save fails. It keeps its owner, group and permissions. (#333) If R is killed during the save, the backup remains, and any function that reads the damaged file says so. If the file still loads, the next save stops and asks you to delete the backup, since rcicr does not delete a file it cannot prove it made. If the file no longer loads, check that the backup holds this experiment (the error shows how), restore it with file.copy(".rcicr-backup", "", overwrite = TRUE, copy.mode = FALSE), check that the file loads again, and only then delete the backup. Leftover .rcicr-staging-* files are incomplete copies and can be deleted. Limits: a writable file in a read-only directory (Unix), and a file name over 228 bytes, are saved as before, without a backup, with a warning. On Windows, a writable file in a folder that refuses new files now stops with an error instead of being saved, as does any save where the backup cannot be made, such as on a full disk. On Windows the backup has its folder's permissions rather than being private to you. Power loss and operating-system crashes are not covered, and two R sessions saving the same file at once remain unsafe. - generateCI() stops when some, but not all, participants IDs are NA. It used to return a CI in which every pixel was NA, with only min/max warnings, and write it as a solid black PNG; every version since 1.0.1 did this. The error names the trials without an ID. Give them one, or remove them from stimuli, responses and participants alike. An all-NA participants still means no grouping, and no valid result changes. (#337) - generateStimuli2IFC() never overwrites a stimulus .Rdata file. Its file name records the minute, so two calls into one folder with the same label and seed in the same minute used to write one file, and the first set's PNGs lost the only record of their noise parameters. The name is now reserved before anything is generated, and the call stops, having written nothing, if that file exists or if another call into the same folder with the same seed, started in the same minute, is still running. The error says to use a different label or stimulus_path, or to delete the file if the set is being regenerated on purpose. The time in the name is now the start of the call rather than its end. (#338) - An img_size the noise scales cannot tile now gets an error that says so. The finest of nscales scales tiles the image with 2^(nscales - 1) patches per side, so img_size must be divisible by that. generateNoisePattern(), and through it generateStimuli2IFC() and simulateNoiseIntensities(), used to fail with "number of items to replace is not a multiple of replacement length". The error now names the constraint, the nearest valid sizes and the largest nscales that fits. Every size that worked still works. (#339) Documentation - Corrected help pages that disagreed with the code: - generateStimuli2IFC(return_as_dataframe = TRUE) returns one row per pixel and one column per trial, not the reverse. - generateCI() takes its scaling constant as scaling_constant, not constant. - batchGenerateCI() also accepts scaling = "matched". - plotZmap() takes a background image matrix, not an .Rdata file. - generateNoisePattern() returns four elements, not two. - ?computeInfoVal2IFC cites the published InfoVal paper (Brinkman et al., 2019) instead of a manuscript in preparation. - computeInfoVal2IFC() always simulates a reference distribution the .Rdata file does not hold; rcicr ships no precomputed ones. - Rewrote the README, both vignettes and the help pages for readability. The "no longer actively maintained" notice is removed from the README. Changes in version 1.4.1 (2026-09-19) Documentation - Removed the obsolete Medium tutorial link; the maintained package vignette is the current walkthrough. Changes in version 1.4.0 Reproducibility impact - InfoVal references for independent base images now use the selected base's saved noise. Previously, with multiple bases and use_same_parameters = FALSE, bases after the first were scored against the first base's reference. Shared-parameter results and the default simulated-response stream are unchanged. This defect did not affect classification images, z-maps, stimuli or responses. The first base's InfoVal is also unchanged for files written from rcicr 0.3.0 onward. For older independent-base files, even the first base can change: their stored noise cannot be reconstructed by the newer generator from the seed alone. Recompute InfoVal for all bases in those files. When saved parameter matrices differ, both computeInfoVal2IFC() and generateReferenceDistribution2IFC() now require the baseimage label used for generateCI(); omitting it reports an error. References are cached separately by base, preserving but ignoring old unscoped norms. Recompute affected InfoVal values from the existing CI object and stimulus .Rdata, using the same base label (here, "second"): computeInfoVal2IFC(ci, "rcic_..._.Rdata", baseimage = "second") No stimuli need regenerating or responses recollecting. The synthetic analysis in #307 characterizes the reference mismatch through modeled shifts across 100 base-image pairs. It does not estimate affected-study rates or bound an individual study's change; recomputing its InfoVal gives that change. - InfoVal references now use the saved noise basis and parameters. Scoring no longer reopens base images or reconstructs the stimuli from their seed. Archived experiments with moved images and uniform bases made with maximize_baseimage_contrast = FALSE can therefore be scored. The obsolete nscales, noise_type and sigma fallback warnings are removed. (#301) References can change for pre-1.1.0 files whose missing settings were previously replaced by defaults, and for pre-0.3.0 files whose parameters could not be reconstructed from the seed. References are otherwise reproduced bit-for-bit when the old reconstruction matched the saved noise and the same RNGkind() is used for the response draws. Changing the RNG kind changes those draws, not the saved noise basis. The CI itself is unaffected; no stimuli need regenerating or responses recollecting. Existing default caches without matching provenance are refreshed once, for both shared and independent bases. Automatic refresh keeps the cached iteration count and preserves the caller's random state. Explicit regeneration still uses the requested count and advances the random stream. Read-only files use the norms in memory and identify the file and, for independent references, the base that could not be saved; they build them again on the next call. That applies to every reference computeInfoVal2IFC() builds, not only a refresh — see Bug fixes. If the norms change, a message says that the returned InfoVal supersedes earlier values. This is a message under every warning setting, including options(warn = 2); it is not collected by warnings() and can be hidden by suppressMessages(). Unchanged norms produce no superseding notice. Provenance includes a full copy of the norms, compared exactly, so an older writer cannot leave a trusted marker on replaced values. Earlier development fingerprints refresh once; no new package dependency is needed. Deliberately seeded caches are retained. Explicitly regenerate any seeded reference built from an incorrect reconstruction, including affected pre-1.1.0 files, before recomputing InfoVal. - generateStimuli2IFC() now ignores an image's alpha channel when reading a base face. Greyscale and RGB images are unaffected. At the default contrast setting, an opaque PNG can differ only in rounding in the stored base-face doubles; its participant-facing 8-bit stimuli and rendered CI are unchanged. A base with varying transparency, or any alpha image with contrast maximization disabled, could previously look visibly distorted in the stored base, stimuli and rendered CI. The CI calculation remains correct for the stimuli shown. If a base needs correcting, inspect and flatten or composite the source image as intended, regenerate the stimuli and collect new responses; do not rerun generateCI() from the existing .Rdata. - Malformed CI trial inputs now report an error instead of silently changing trial assignments. generateCI() requires one participant ID per trial when grouping is enabled. It, generateCI2IFC() and computeCumulativeCICorrelation() reject empty, missing, nonfinite, nonpositive, fractional or out-of-range stimulus IDs, and factor, character or logical IDs. Direct generateCINoise() calls reject mismatched parameter rows and responses. Valid numeric inputs and the all-NA no-grouping convention are preserved. If an existing script now errors, recover the correct trial IDs and participant assignments from the experiment records and recompute affected CIs and downstream results; do not pad or recycle IDs to satisfy the check. (#294, #300) - A classification image with no range now renders as a uniform neutral image instead of NaN. Three cases divided by a zero range. generateCI() under its default scaling = "independent" did so for a CI whose pixels are all exactly zero — what exactly cancelling responses produce — making every pixel of $scaled and $combined NaN. Under scaling = "matched" the same happened for any CI with no variation at all, a uniform non-zero one included. autoscale() did so when every CI in its list was zero, in $scaled only: it leaves the caller's $combined untouched, as always. All three now fill the unmasked pixels with a neutral value and warn: 0.5 for independent and autoscale(), which is what constant scaling already returned for the same input, and the midpoint of the base image's range for matched, which renders into that range. Masked pixels keep their NA and the unscaled $ci is unchanged. Every value that changes was NaN before. No finite number moves, no CI with any variation in it is affected, and a result that was NaN cannot have carried a published number. (#303) Behaviour changes - generateStimuli2IFC() now writes stimuli for every base image when it also returns a data frame. With more than one base image and return_as_dataframe = TRUE, only the first base image's PNGs were written, although the call succeeded and the .Rdata file recorded every base image's parameters. A stimulus set generated that way is incomplete: re-run the same call with the same seed to write the missing files. Nothing already collected is invalidated. The stimuli that were written are unchanged, as is the returned data frame, which still holds one noise image per trial for the first base image. (#302) Bug fixes - Parallel workers now start whatever the session's OutDec and scipen are. With both options(OutDec = ",") and a scipen negative enough to force scientific notation — neither alone does it — every call that spawns workers failed, after waiting out the whole connection timeout and then naming the workers rather than the cause: generateStimuli2IFC(), generateCI() and friends with n_cores > 1, and reference generation. parallel renders a worker's port into a command line as text, where those options wrote 11000 as 1,1e+04 for the child to read as NA. Both are now neutralised across cluster construction and restored immediately afterwards. They govern rendering and never arithmetic, so no numeric output changes and no existing result is affected. (#316) - Scoring a read-only archive no longer fails when it has no cached reference to reuse. computeInfoVal2IFC() simulates the reference before storing it, then asked to store it even where the file could not be written, so a call that had already computed its answer ended in cannot open the connection. Three cases reached it: a stimulus file with no cached reference, force_gen_ref_dist = TRUE, and — for saved base images with different noise parameters — any archive predating per-base caches, whose unscoped reference_norms older versions of rcicr scored from without writing anything. Only an unsolicited refresh of a stale cache had the fallback. All of them now use the computed norms in memory, say which file and base could not be saved, and leave the archive untouched. The InfoVal is the value a writable run returns; nothing about how references are simulated or cached changed. A direct generateReferenceDistribution2IFC(save_rdata = TRUE) still reports an error, because there the save is what was asked for. Performance - generateReferenceDistribution2IFC() no longer re-copies its stimulus data on every simulated response set (#306). Converting it once per call speeds up reference simulation, and so the computeInfoVal2IFC() calls that have to generate one. The gain is largest at small image sizes and narrows as the matrix-vector product itself becomes memory-bound. Reference norms and the random-number stream are unchanged. Documentation - ChangeLog is frozen at 1.0.1 and is no longer updated. It keeps the record of everything before 1.1.0, including the whole CRAN era up to the archived 0.3.4.1. NEWS.md is the changelog from 1.1.0 onward and the only one R indexes, so news(package = "rcicr") is unaffected. Entries for 1.1.0 to 1.3.0, which only pointed here, were removed from it. Changes in version 1.3.0 (2026-09-02) Behaviour changes - A decorated z-map on a device too small for it now says so, and can be made to fit. plotZmap(decoration = TRUE) needs room for margins, labels and the colour scale — about 160px at the default text size on Linux and macOS, and about 200px on Windows, whose graphics device is 96 ppi where theirs is 72 — and below that base R stopped with figure margins too large, naming neither rcicr nor a way forward. It now stops with a message giving the size, the minimum, and the three ways out. plotZmap() gains pointsize and generateCI() gains zmappointsize: the decoration is measured in lines of text, so a smaller size fits it onto a smaller image. This matters most through generateCI(), which sizes the z-map to img_size, so a stimulus set below about 160px could not produce a decorated z-map at all — a 128px set now can, with zmappointsize = 6. Both arguments default to the graphics device's own 12, so every existing call renders exactly as before; a 512px decorated z-map is byte-identical. - plotZmap() no longer depends on raster, and its ... arguments now go to graphics::image() instead of the raster package's plot method. col behaves the same way in both (and now actually works — see the bug fixes below); a call passing an argument specific to that method will now be rejected as unused. Dropping the dependency also removes terra, sp and Rcpp, and with them the GDAL/GEOS/PROJ system libraries that every Linux CI job had to install before it could start. The z-map itself renders identically — the undecorated figures generateCI() writes are pixel-for-pixel the same as before, within colour quantisation, and a golden reference rendered by the old raster code is committed as a test fixture to keep it that way. The palettes are unchanged, including the quirk that a z-map drawn over a background image uses the default palette rather than the viridis one. - A decoration = TRUE z-map is laid out slightly differently. The colour bar is now drawn by hand rather than by raster, and the map is a few pixels wider at 512px. If you regenerate a decorated z-map figure, it will not be pixel-identical to one saved with 1.2.3 — the same is already true of regenerating it on a different operating system, and for the same reason: what a graphics device paints is not part of what this package computes. No z-score, classification image, scaling result or informational value changes, and generateCI()'s own z-map figures are undecorated and unaffected. See ?plotZmap, "Reproducibility across platforms". - generateCI(mask = matrix(NA, 1, 1)) now reports the malformed mask instead of silently ignoring it. The internal test for "was a mask supplied?" asked only whether the argument was a single NA — which a one-cell NA matrix, a one-element array(NA) and a list(NA) all are. Such a mask was mistaken for the NA default, discarded without a word, and an entirely unmasked classification image came back. A call that passes a mask and gets an unmasked CI is the failure worth catching early; it now stops in the same mask validation every other malformed mask reaches. The sentinel is now specifically an atomic scalar with no dimensions, so mask = NA still means "no mask" — it is the default, and every unmasked call relies on it — while matrix(1, 1, 1), larger matrices, PNG paths and NULL are unchanged. - generateStimuli2IFC() now checks base_face_files before it generates anything, and names the entry it cannot use. Four inputs used to get past the old check and fail from inside a parallel worker with attempt to select less than one element in get1index, which names neither the argument nor the file: a list with no names, a list with some names missing, an empty list, and an element that is not a single file name. They now stop immediately with a message saying which entry is wrong and why. One of them could previously appear to work. A base_face_files with two entries under the same name silently dropped all but the first — the loop looked each name up by string, so list(face = 'a.png', face = 'b.png') produced one set of stimuli, from a.png, and nothing at all from b.png. That is now an error naming the duplicated name. If you have a script that relies on it, the stimuli it produced were never what the call asked for; give each base image its own name. - The PNG-or-JPEG test now looks at the file extension rather than anywhere in the path. It was grepl('png|PNG', filename) against the whole path, so a JPEG stored under a directory called png was handed to png::readPNG() and died with file is not in PNG format, blaming the file for a choice the package had made. Files are now recognised by a .png, .jpg or .jpeg extension, case-insensitively. A base image whose extension does not say what it is is rejected up front, by name, instead of reaching a reader that cannot parse it. - The .Rdata validation errors now name the version of rcicr that wrote the file. generateCI() and computeCumulativeCICorrelation() already said which field a file was missing; they now also say where the file came from, which is what turns "this file has no stimuli_params" into "it predates the version that added it — regenerate the stimuli, or install that version". The version is read tolerantly, because the field cannot be taken at face value: p$generator_version is preferred over the top-level generator_version, which every release from 0.4.0 through 1.1.0 recorded as a hardcoded 0.4.0, and a file that only claims 0.4.0 is reported as unknown rather than as 0.4.0. A file with no version field at all reports the absence and stops there — an absent field is equally a file older than 0.4.0, a truncated one, or one rcicr never wrote. generateReferenceDistribution2IFC()'s two warnings about a missing nscales or noise_type were reworded for the same reason. They said the file "was written by a version of rcicr that did not save" the field, which an absence does not establish; they now report what is missing, and keep the version as context rather than as a conclusion. The advice is unchanged, and was always right either way: regenerate the stimulus set with this version. - A stimulus file with gabor noise and no saved sigma now says so. generateReferenceDistribution2IFC() assumes the historical default of 25 when a file predates 1.1.0 and lacks the field, which it has always done silently — unlike the loud warnings for a missing nscales or noise_type. For gabor noise that silence hid the same hazard those warnings exist for: sigma is what shapes the Gaussian mask, so guessing it wrong rebuilds the null on a different noise basis than participants saw, and the resulting InfoVal is wrong. On a 1.0.1 gabor stimulus set the reference norms move from 0.681/0.689/0.680 at sigma = 25 to 0.615/0.620/0.626 at sigma = 10. Sinusoidal files are unaffected and stay silent, which is the point of doing this by noise type rather than by field: sigma reaches the basis through generateGabor() alone, so for sinusoidal noise the norms are identical whatever it is, and a warning would be pure noise on the far more common legacy file. Nothing warns that did not previously produce a wrong answer, and no numeric output changes. - plotZmap(mask = ...) now accepts a mask with an alpha channel. It previously required every colour channel of a multi-channel PNG mask to match exactly, so a greyscale-plus-alpha or RGBA mask whose alpha plane happened to differ from its colour planes was rejected — alpha carries no colour information and is now always ignored, matching generateCI(mask = ...). A rectangular zmap/mask pair continues to work as before. plotZmap(mask = ...) also now rejects a mask that is neither a string nor a matrix with a clear error, instead of failing later inside png::readPNG() with an unrelated message. - plotZmap(mask = NA) now means "no mask", as it already does in generateCI(). The two functions detected a supplied mask differently — plotZmap() asked only whether the argument was non-NULL — so the same sentinel meant opposite things: generateCI() read NA as "no mask" while plotZmap() passed it on and stopped with The mask argument is neither a string nor a matrix!. NaN behaved the same way. Both now render an unmasked z-map. No call that worked before changes: the inputs affected all raised that error. - generateStimuli2IFC() no longer saves trial in the .Rdata file. It was the loop counter left over from stimulus generation — always equal to n_trials, which is already saved. Nothing in the package or the documented contract reads it. Existing .Rdata files that contain trial continue to work; the field is simply ignored on load. Reproducibility impact - Individual-CI PNGs written by generateCI(save_individual_cis = TRUE) carried the wrong participant's name, and are now named correctly. The per-participant loop selects each participant's trials by sorted order and took the output filename from order of appearance. At each position where those two orders differ, the file in /individual_cis was given another participant's ID. The pixels were always right; only the names were wrong. Affected: a direct call to generateCI(participants = ..., save_individual_cis = TRUE) where the participants vector is not already in sorted order. Such a call produced correct images under incorrect filenames, so a figure published as participant p2 may be someone else's classification image. Do not assume tidy data was safe — the common case is affected. Sorting is lexical, so text labels like "p10" sort before "p2". A study whose participants appear in collection order p1, p2, ..., p10 is therefore affected from the tenth participant onward, even though nothing about that data is out of order in any ordinary sense: | participants, in collection order | affected | |---|---| | "p1" … "p9" | no | | "p1" … "p10" or beyond | yes | | "p01" … "p12" (zero-padded) | no | | "1" … "12" (character) | yes | | 1 … 12 (numeric) | no | Unpadded text IDs and ten or more participants is the ordinary shape of a reverse correlation study, so if that describes yours and you used the call above, check rather than assume. Not affected — and this is the documented route, so most analyses are in this group: - batchGenerateCI(), batchGenerateCI2IFC() and generateCI2IFC(). None of them exposes participants or save_individual_cis at all; they split the data themselves and name each image from the grouping value directly, never reaching the code that was wrong. Per-participant classification images computed the way the package documentation has recommended since the CRAN era — batchGenerateCI2IFC(), and batchGenerateCI() in the current vignette — were correctly labelled throughout. - Everything generateCI() returns. The group classification image is a mean across participants and so does not depend on their order; the per-participant stack, and every z-map built from it, were internally consistent throughout. No returned value, and no number in any .Rdata file, ever changed. - Any call that left save_individual_cis at its default FALSE. No individual-CI file was written, so there was nothing to mislabel. - Participants already in sorted order, where the two orderings coincide. - Every CRAN release. rcicr was on CRAN from July 2014 until it was archived in June 2021; the last release, 0.3.4.1 (July 2016), predates the save_individual_cis option entirely. install.packages('rcicr') never produced a mislabelled file at any point. If you have published from individual-CI images, check your analysis scripts. Two steps, neither of which requires re-running anything. The individual-CI filename advisory carries the fuller version of this, including what the mislabelling does to a second-stage analysis and how much of an effect survives it; where the two differ, that page is kept current and this section is not. Step 1 — did you use the relevant call at all? Look for an individual_cis/ directory in your output. Nothing else in the package creates it, so finding it means the direct call ran. For output made before 1.3.0, continue to step 2. If your per-participant images came from batchGenerateCI() or batchGenerateCI2IFC(), those cannot reach the defect. Check the output rather than the script. save_individual_cis is the sixth formal of generateCI(), so a call can set it positionally — generateCI(stim, resp, "face", rdata, pids, TRUE, ...) — and write those files without the argument name appearing anywhere; do.call() hides it the same way. Searching your scripts and finding nothing does not clear an analysis. Not finding the directory clears it only if the output still sits where the run left it. Renamed, archived or moved, it will not answer to that name, and the individual files are then recognisable but not conclusive: ci_.png is what the affected call writes, and also what generateCI(save_as_png = TRUE, filename = "") writes for a group CI one directory up. Step 2 — if the directory is there, was your participants vector in sorted order? Note "sorted" means lexically sorted for text labels, per the table above — sorting your data frame by a "p1"-style column does not make it safe once you reach ten participants. Where you still have the vector, one expression settles it: identical(unique(participants), sort(unique(participants))) # TRUE = names were correct Two conditions on that TRUE, both of which change what sort() returns: use the original participants object rather than a character copy, since a factor carries its own level order and sort() follows it; and run it under the collation the original analysis ran under, since identifiers mixing case, accents or punctuation sort differently by locale. Numeric identifiers are not exposed to the second, numeric sorting being locale-independent, and p1/p12 are not realistically exposed either. Lowercase ASCII is no guarantee in general, though, since some locales collate letter pairs as units (Czech ch, Danish aa). If the output is long gone and only the script survives, search it for individual rather than the full argument name, and read by hand any generateCI() call passing six or more arguments positionally. Recovering existing output is a rename, not a re-run. The mapping is exact: the file named unique(participants)[i] holds the classification image of sort(unique(participants))[i]. old <- unique(participants) # the names the files were given new <- sort(unique(participants)) # the participants they actually hold Every release from 1.0.1 (January 2023) through 1.2.3 (August 2026) carries the defect, as does every install from the development branch from 15 August 2017 onward — the date it was introduced, in the commit that first made individual-CI saving work at all. There were no releases between 2017 and 2023, so a copy obtained in those years came from the branch and is affected too. Getting it from GitHub took a deliberate step, until December 2021. The default branch was master, which carried an older repository layout with no DESCRIPTION at its root, so install_github('rdotsch/rcicr') did not install anything at all — it failed. The affected code was on the development branch, reached only by asking for it: install_github('rdotsch/rcicr', ref = 'development'), which the README of the time labelled "AT YOUR OWN RISK" and specifically advised against for analyses meant for publication. development became the default branch on 2021-12-28, and from then a plain install returns affected code. If you need to work out which version a stored analysis actually used, the version number alone will not tell you: 0.4.0 sat on the development branch for five years, with the defect entering partway through. A note listing every release, its date, whether it came from CRAN or GitHub, and what "the latest version" gave you in each window is at https://github.com/rdotsch/rcicr/blob/main/notes/individual-ci-mislabelling.md. - computeCumulativeCICorrelation() with a masked targetci now returns numeric correlations where it previously returned all-NA. No existing analysis could have used the old result — it carried no information — but code that checked for NA on the returned curve will see a different answer. Unmasked targets are bit-identical. Bug fixes - generateCI(save_individual_cis = TRUE) names each PNG for the participant it was actually computed from. The loop selects participants by sorted level order and the filename was taken from order of appearance, so the two disagreed for any data not already sorted by participant. See "Reproducibility impact" above for how to tell whether files you already have are affected, and how to correct them without recomputing anything. - computeCumulativeCICorrelation() now returns real correlations when targetci was generated with a mask. generateCI() stores NA in every pixel a mask excludes, and the correlation was taken over all pixels (use = "everything"), so a single masked pixel made every point on the curve NA however strong the signal. Correlations are now computed over the unmasked pixels only. A fully masked target (every pixel NA) still returns an all-NA curve — there are no complete pairs to correlate. - plotZmap(col = ...) works. Supplying a palette is how ?plotZmap has always told you to change the colours, and doing it stopped the call with formal argument "col" matched by multiple actual arguments before anything was drawn — the function passed its own col alongside yours. This affected every released version; the argument is now taken as an override, and is used for the colour bar as well as the map. - A base image with no contrast no longer becomes an all-NaN base image. Under maximize_baseimage_contrast = TRUE, the default, generateStimuli2IFC() rescales with (img - min(img)) / (max(img) - min(img)) — which is 0/0 when every pixel is the same value. The resulting NaN base face was written into the .Rdata with no error and no warning, every classification image computed from that stimulus set inherited it, and the stimuli themselves came out uniformly black, png::writePNG() clamping NaN to zero. It now stops with an error naming the file. A photograph is never uniform, so this bit synthetic and accidentally-blank base images — but it failed silently, and the symptom appeared a long way from the cause. The error fires only under maximize_baseimage_contrast = TRUE: a flat base image is perfectly usable with the rescale switched off, and the message says so. - computeCumulativeCICorrelation() reads pre-0.3.0 stimulus files again. Files written before rcicr 0.3.0 (January 2015) store 4096 contrast parameters per trial where only 4092 patches exist, and generateCI() has truncated the four unused columns for years. computeCumulativeCICorrelation() did not, so on such a file the extra columns reached generateNoiseImage() as a length mismatch and it aborted with "number of parameters doesn't equal number of patches" — the cumulative-correlation curve could not be computed at all. It now applies the same truncation. Files from 0.3.0 onward already have 4092 parameters and are unaffected. The same fix keeps a single presented stimulus two-dimensional, so a one-stimulus call — which aborted with "incorrect number of dimensions" on any file — now returns its (single-point) curve. - generateCI() with participants works again on more than one core. Supplying participants without targetpath — the normal thing to do when you are not saving individual CIs — stopped with argument "targetpath" is missing, with no default as soon as n_cores was greater than 1, which is the default on any multi-core machine. foreach inspects the loop body and fetches every variable it mentions, including one used only in the branch that saves individual CIs, and that branch is precisely the one that cannot run when you have not asked for those files. Single-core calls were unaffected, as was saving individual CIs with a targetpath. - The progress bar moves again during parallel runs. generateStimuli2IFC() and generateCI() build their progress bar in your session but were advancing it from inside the parallel loop, where each worker holds a private copy — so under the default (ncores/n_cores greater than 1) the bar sat at 0% for the whole job and jumped to 100% at the end. On a 770-trial 512px set that is a long time with nothing to watch. Affects stimulus generation, per-participant CIs, and t.test z-maps. Serial runs were never affected and are unchanged. This required swapping the foreach backend from doParallel to doSNOW, which is the one that can report task completions back to the parent session. doParallel is no longer a dependency; doSNOW and snow take its place, for slightly less installed than it took. Nothing about how the loops compute changes, and no numeric output changes. - The base_face_files type check raises an error you can actually read. It wrote its explanation to stderr() and then called stop() with no arguments, so the condition it raised carried an empty message: conditionMessage() returned "", and anything wrapping the call — a Shiny app, a batch script, knitr — caught an error it could not report. Under sink() or capture.output(type = "message") the explanation could be separated from the failure entirely. The text now travels with the condition. - A base image that cannot be read is reported with the file it came from. The reader's own complaint (file is not in PNG format, and the like) is kept and prefixed with the entry's name and path, and a file that does not exist is now reported as missing rather than as unopenable. - Base images are validated before the noise basis is built and before the output directory is created. A mistyped path used to cost the full noise-pattern computation — appreciable at the default 512px — and left an empty stimulus directory behind before reporting the problem. No numeric output changes. Every check here either replaces an error with a clearer error, or rejects input that could not have produced correct stimuli; a call that succeeds today produces exactly what it did before. - A multi-channel PNG mask one pixel wide or tall is now rejected instead of silently masking everything. Dropping the colour channel from such a mask also dropped its singleton spatial dimension, leaving a plain vector whose absent dimensions made the size check pass vacuously; the mask was then recycled by linear indexing. A 1-by-8 RGB mask against an 8-by-8 stimulus set returned an entirely NA classification image with no error or warning. It now fails the size check and says so. Masks of any other shape are unaffected. - generateCI(mask = ...) no longer crashes on a greyscale-plus-alpha PNG mask. The internal mask importer hardcoded three colour channels, so any 2-channel PNG (the form png::readPNG() produces for 8-bit greyscale-plus-alpha) failed with subscript out of bounds rather than a clean error — plotZmap(mask = ...)'s separate, now-removed mask code already accepted the same file. Mask import is now shared between the two functions (#185), so both accept a 2-channel mask, and a 4-channel (RGBA) mask continues to work exactly as before. The mask-size-mismatch message now names what the mask is being checked against ("stimuli" from generateCI(), "z-map" from plotZmap()) instead of always saying "stimuli", and reports the mask's dimensions in the same row-by-column order as the target's rather than transposed. Documentation - ?computeCumulativeCICorrelation explains what its curve ending at 1 does and does not mean. With no targetci, the final CI it compares against is built from the same un-aggregated trials as the curve, so wherever the evaluated trials reach the last one the curve ends at 1 — self-consistency, not convergence. That is every call at the default step = 1, though a larger step can stop short of the final trial and end below 1, and responses that cancel exactly give a constant CI and an all-NA curve. That final CI is identical to generateCI()'s where every stimulus was presented the same number of times, and differs where repeat counts vary, because this function weights each trial equally while generateCI() weights each unique stimulus equally. Measured on an 8-trial set with counts 4/2/1/1, the two correlate at 0.77. Pass targetci = generateCI(...) when you want the curve to describe the CI you will report. Nothing changed in what the function computes. - ?generateCI documents the weighting of repeated stimulus presentations. When participants is NA, repeated presentations are collapsed before building the CI: each unique stimulus gets equal weight, regardless of how many times it was presented. With equal repeat counts this changes nothing; with unequal counts it is a different estimand from weighting each trial equally, and the difference can be substantial. Previously this was described only as a performance optimisation in a code comment. - There is now a documentation website: https://rdotsch.github.io/rcicr/. The function reference, both vignettes and this changelog are readable without installing the package first. It is generated from the same sources — README.md is the home page, so "How it works" and "Anatomy of the .Rdata file" are not maintained twice — and rebuilt by GitHub Actions on every push, so it cannot drift from the code. - The repository now carries a CITATION.cff, so GitHub offers a "Cite this repository" button. citation("rcicr") is unchanged and still reads inst/CITATION, which remains the citation of record; the new file is generated from it and DESCRIPTION by cffr, and carries it as preferred-citation. The two cannot drift apart: CI regenerates the file, compares every field, and validates the result against the CFF schema. - The install instructions now install a release rather than the development version. remotes::install_github('rdotsch/rcicr') takes the tip of main, which carries unreleased changes; the README.md and walkthrough-vignette instructions now lead with @*release, and show @v1.2.3 for installing one specific version — worth recording in an analysis script, since a classification image is only reproducible against the version that computed it. - ?generateGabor now documents generateGabor(). Its example called generateSinusoid(), so the help page and the reference site demonstrated a call that never reached the function being documented; @return described a sinusoid rather than a gabor patch, and @param sigma was an unfinished sentence that did not say what the value means. sigma is the standard deviation of the Gaussian mask in pixels — measured, not inferred: the mask falls to exp(-1/2) at 24.5 px from centre for sigma = 25. The function itself is unchanged. Internal - The pre-0.3.0 0-indexed noise path is now pinned against an independent oracle. Reading a .Rdata file whose patch indices start at 0 takes a backward-compatibility branch in generateNoiseImage() that drops the 0-indexed cells and recycles a too-short weight vector — which looks like it could misalign the whole image. It does not: the last patch layer is all-zero, so the recycled values land only where the patch is zero and are multiplied away. A test confirms the output equals the honest "one patch not shown" result exactly, so no change to the indexing can silently reintroduce a misalignment. No behaviour changes. - Stimulus sets generated by 1.0.1 and 1.1.0 are now covered by tests. The suite carries .Rdata files those releases actually wrote — generated by installing each tag, not reconstructed — and checks that the current generateCI() still reads them and produces a classification image. A 1.0.1 file predates nscales, so generateReferenceDistribution2IFC() has to assume it: the test asserts the null it builds is bit-identical to one from a file that states nscales outright, because a null built on the wrong noise basis finishes just as happily and is wrong. (The parallel sigma fallback only affects Gabor noise, which these sinusoidal fixtures do not exercise — tracked separately.) Every other fixture is built by the current generator, so nothing before this could catch a field this version expects and an older one never saved. It runs on every platform with no network: the fixtures are 205 KB (1.0.1) and 45 KB (1.1.0). Changes in version 1.2.3 Documentation only. Nothing this package computes has changed — no function, argument, return value or number differs from 1.2.2, and no analysis script needs revisiting. The release exists because the package-level help page, ?rcicr, had gone stale enough to contradict the release before it. It was a hand-maintained .Rd file that roxygen never touched, so the sweeps behind 1.2.2 — which all worked from R/ — went straight past it. Every code snippet on it was wrong: - Two of the three example calls, generateStimuli2IFC(base_face_files, n_trials = 770) and generateCI2IFC(stimuli, responses, baseimage, rdata), would now error: they omit the write paths that 1.2.2 made required. - The third, autoscale(cis, saveasjpegs = TRUE), named an argument that has never existed under that spelling. It is save_as_pngs. - The page twice promised output "saved as jpegs to a folder called stimuli in your current working directory" — the writing-by-default behaviour that 1.2.2 removed, and in a format the package does not write. - Its \examples section was a single commented-out line, #simple examples will be added soon., left over from 2016. - It carried a hand-typed Version: 0.4.0 and Date: 2017-07-25, five releases and nine years out of date. The page is now generated by roxygen from R/rcicr-package.R, so its title, description, author and URLs come from DESCRIPTION and cannot drift from it again. The version and date table is gone rather than corrected — the way to keep a fact current is to stop writing it down twice. What remains is a short pointer to vignette("reverse-correlation-walkthrough") and to the three functions a new user starts with; the walkthrough itself lives in the vignette and README.md, which are tested on every build. The page also now lists all three key references — Dotsch & Todorov (2012), Brinkman, Todorov & Dotsch (2017), and Dotsch, Wigboldus, Langner & Van Knippenberg (2008) — each with a DOI. The 2008 paper had been on the old page and was the only one not carried anywhere else. ?rcicr and package?rcicr both still work. Changes in version 1.2.2 This release exists to answer the changes CRAN asked for when reviewing the previous submission. Nothing it changes affects a number this package computes: classification images, scaling, z-maps and informational value are identical to 1.2.1, and the release gate confirms that against both 1.2.1 and 1.0.1. Breaking changes - Functions that write files now require you to say where. stimulus_path (generateStimuli2IFC()), targetpath (generateCI(), generateCI2IFC(), batchGenerateCI(), batchGenerateCI2IFC(), autoscale(), plotZmap()) and zmaptargetpath (generateCI()) have lost their defaults. They used to be ./stimuli, ./cis and ./zmaps, which meant a default call created directories in whatever your working directory happened to be — writing to your filespace without being asked, which CRAN policy does not permit. What to change in your scripts. If you relied on the old defaults, name them: # before generateCI(stimuli, responses, "face", rdata) # after generateCI(stimuli, responses, "face", rdata, targetpath = "./cis") You will not silently get files somewhere new — a call that would have written to a default path now stops with an error naming the argument to supply. If you do not want files at all, save_as_png = FALSE (or save_as_pngs = FALSE for autoscale()) needs no path. Bug fixes - batchGenerateCI() no longer produces a spurious CI for rows with no group. Rows whose by column was NA were kept and collapsed into an extra group named after NA, so a data frame with any missing grouping value returned one more classification image than it had groups — computed from whatever rows happened to be missing that value. batchGenerateCI2IFC() has always dropped those rows; the two now agree. - generateCI(mask = ) accepts a logical matrix. The matrix branch tested typeof(mask) == 'double', so a mask built the obvious way — as TRUE/FALSE rather than 1/0 — fell through to The mask argument is neither a string nor a matrix!, despite the documentation describing exactly that form. It is now tested with is.matrix(). - generateCI() and computeCumulativeCICorrelation() no longer print the entire base image when they cannot find stimulus parameters. The "No parameters found for base image" error named the base image matrix where its label was meant. Because paste0() is vectorized, this did not paste one matrix into one message — it built one complete message per pixel, so the error came back as 1,024 concatenated copies at a 32x32 base image (8,190 characters) and roughly 7 MB at the 512x512 size researchers actually use, with the reason for the failure buried inside it. The message now reads, in full, No parameters found for base image: