NEWS.md

rcicr 1.5.0 (2026-09-25)

Reproducibility impact

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.

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

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("<file>.rcicr-backup", "<file>", overwrite = TRUE, copy.mode = FALSE), check that the file loads again, and only then delete the backup. Leftover <file>.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.

Documentation

rcicr 1.4.1 (2026-09-15)

Documentation

rcicr 1.4.0 (2026-09-13)

Reproducibility impact

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"):

r 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.

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.

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

Bug fixes

Performance

Documentation

rcicr 1.3.0 (2026-08-18)

Behaviour changes

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.

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.

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.

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.

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.

Reproducibility impact

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:

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_<participant>.png is what the affected call writes, and also what generateCI(save_as_png = TRUE, filename = "<participant>") 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:

r 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].

r 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.

Bug fixes

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.

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.

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.

Documentation

Internal

rcicr 1.2.3 (2026-08-08)

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:

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.

rcicr 1.2.2 (2026-08-07)

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

What to change in your scripts. If you relied on the old defaults, name them:

``` r # 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

Only the text of an error changed. No function's return value, arguments or numeric output are affected, and the condition that triggers the error is unchanged — if your analysis script runs today, it behaves identically.

Documentation

Internal

No file this package has ever written triggers the problem, so no result changes and no analysis needs revisiting — the guard is preventive. It is worth having because the one collision that did occur (the z-map sigma, fixed in 1.2.0) was created by adding a field to the file, not by adding an argument, so an argument that is safe today stops being safe without anything in the function changing. The case now closed in computeInfoVal2IFC() is the one that would have mattered most: target_ci is read at the very end to compute the CI norm, and after a second load(), so a file carrying that name would have scored a different classification image and returned a plausible number rather than an error.

rcicr 1.2.1 (2026-07-28)

No user-facing changes. Nothing this package computes differs from 1.2.0 — no function, argument, return value or number has changed, and no analysis script needs revisiting.

The release exists because the 1.2.0 source tree does not pass R CMD check on macOS. The fault was in the package's own test suite, not in the package: a test asserted properties of a rendered PNG that belong to the graphics device rather than to what was drawn, and those properties differ between macOS and Linux. No released function was ever affected. A package still has to pass its own checks on the platforms CRAN builds for, which is what this release restores.

Documentation

The practical advice, now stated in both places: compare numbers, not rendered figures, when checking that an analysis reproduces. A z-map image that differs pixel-for-pixel on a colleague's machine is not a different result. Every other PNG the package writes — stimuli, classification images, autoscaled classification images — is written directly from the pixel array and carries no such dependence.

Internal

rcicr 1.2.0 (2026-07-28)

Upgrading from the CRAN version? The last release on CRAN was 0.3.4.1, before the package was archived in 2021. 1.0.1 and 1.1.0 were GitHub-only releases made in the meantime, so the 1.1.0 section below applies to you too — it is where the bulk of the bug fixes are.

Reproducibility impact

In practice this affects nobody. 1.1.0 was a GitHub tag that stood for about a day and was never on CRAN, so almost no stimulus set carries the field that triggers it. Files from 1.0.1 and earlier have no sigma at all and were never affected. Only z-maps are involved — classification images, scaling, InfoVal and every saved number are untouched. It is recorded here because it did change a number, and because if you are the one person who generated stimuli that day, regenerating the z-map is a one-line rerun.

Every argument is now kept across the load(), so a field added to the .Rdata later cannot quietly capture another one. Found by tools/compare-release-output.R, the release gate introduced in this version — the first bug it caught.

Behaviour change

Who is affected. Only direct calls to plotZmap(mask = ...). generateCI() does not pass mask to plotZmap() — it masks the classification image itself, via a separate and working code path — so z-maps produced through the normal pipeline are unchanged, and no stored numbers change anywhere. If you have been passing a mask and your z-maps looked unmasked, that is why; they will now come out masked, and the earlier images were wrong about which regions carry signal.

A second bug is fixed alongside: the conversion to boolean set every cell to FALSE whatever you passed, so even once applied the mask would have masked nothing.

If you built a mask by reading ?generateCI rather than by looking at your output, check it: 0/black/FALSE is the region that gets masked away.

New features

Existing calls are unaffected. The default (NULL) issues no set.seed() call at all, so the reference distribution is byte-identical to what earlier versions produced. Verified against norms generated before the change, not merely assumed.

In computeInfoVal2IFC(), passing response_seed forces the reference distribution to be regenerated even when the .Rdata file already holds one, and the result is deliberately not written back — a one-off check of the Monte Carlo error cannot silently become the number every later analysis of that stimulus set reports.

Bug fixes

No result changes: nothing in the package has ever read this field. If your own code does, note two things. Existing files cannot be trusted to say what wrote them, so treat a stored '0.4.0' as "unknown, somewhere between 0.4.0 and 1.1.0" rather than as a version. And the field is now a package_version object rather than a character string, so compare with utils::packageVersion() or numeric_version(), never as text — '0.10.0' < '0.4.0' is TRUE when compared as strings.

Documentation

rcicr 1.1.0 (2026-07-27)

First release since 1.0.1, and the version submitted to CRAN to reinstate the package after its 2021 archival. The minor bump rather than 1.0.2 is deliberate: some of the changes below alter behaviour rather than only repairing it. The public API is unchanged — no function, argument or argument meaning was removed or redefined — so a 2.0.0 is not warranted. Anyone re-running an old analysis script should read the "Reproducibility impact" section below.

Bug fixes

Performance and dependencies

Reproducibility impact — read this if you have published or in-progress results

This release fixes several long-standing bugs. Most of them turn a crash into working behaviour, and therefore cannot change any result you already have — if the old code errored, there was no number to change.

Only two fixes can change a value you may already have reported. Both are listed first, with the exact conditions under which they apply.

A golden-master test (tests/testthat/test-regression-baseline.R) pins the numeric output of the default pipeline — noise basis, classification image, scaling, and infoVal — to the values produced before these fixes. It passes, which is the evidence for the claim that default-configuration results are unchanged.

Results CAN differ

1. infoVal, if you used a non-default nscales (issue #81)

generateStimuli2IFC() did not save nscales or sigma into the .Rdata file. generateReferenceDistribution2IFC() re-generates the stimulus set from that file to build the infoVal null distribution, so with nscales missing it silently fell back to the default nscales = 5 — building the reference distribution on a different noise basis than the stimuli participants actually saw.

2. infoVal, if you passed force_gen_ref_dist = TRUE (issue #113)

The flag was silently ignored whenever a reference_norms vector already existed in the .Rdata file, so the reference distribution was reused rather than recomputed.

Results cannot differ

These fixes only affect code paths that previously errored out, so no previously obtained result changes:

Changed, but below any scale that can matter: the patch average

generateNoiseImage() now averages patch layers with rowMeans(..., dims = 2) rather than apply(..., 1:2, mean). These compute the same quantity but sum in a different order, so they are not bit-identical: they differ by about one unit in the last place, ~1e-19 in absolute terms on pixel values of order 0.01.

This is a different class of thing from the rbernoulli case below. That one would have changed the random stream — a large, systematic divergence. This is floating-point summation order, and it was checked against an independent oracle (the average written as an explicit triple loop, using neither apply() nor rowMeans()) across noise types, spatial scales and seeds: both the old and new forms sit ~5.6e-17 from that oracle. Neither is "more correct" than the other.

Deliberately unchanged: the random number stream

purrr::rbernoulli(n, p) (deprecated) is internally runif(n) > (1 - p). The obvious modern replacement, rbinom(n, 1, p), draws from the stream differently — verified across 150 seed/probability combinations that the two diverge. Swapping it in would have silently changed every reference distribution, and therefore every infoVal, computed from a given seed. The runif form is used instead, verified bit-identical to the old behaviour.

Unchanged and verified

Changes before 1.1.0 are recorded in ChangeLog, which is frozen at 1.0.1 and covers the package back to 2014, including its CRAN era.



Try the rcicr package in your browser

Any scripts or data that you put into this service are public.

rcicr documentation built on Sept. 27, 2026, 5:07 p.m.