anicore 0.8.0 (2026-08-28)
Changed
-
The package is renamed from
aniframetoanicore(#84). It is no longer one class’s home: it declaresaniframe,anieventand — as #84 settles — the types the domain packages build on, while producing almost none of them. A package named after one of its entries was going to keep getting stranger as angles (#83), orientation (#46) and masks (#11) arrive.The
aniframeclass keeps its name, as doas_aniframe(),is_aniframe()and every other function. Only the package changes:library(anicore),anicore::, andinstall.packages("anicore").Done now rather than at 1.0.0 because a rename only ever gets more expensive, and there are no external users yet to carry the cost.
?aniframeis the constructor again. The package documentation had claimed the same help topic, so the two were merged into one page; it is now?anicore.
Added
-
An aniframe can be indexed by a column that is not called
time(#109). Avariables_indexmetadata field names the single column each row is positioned by;get_index()reads it,set_index()changes it and re-orders the frame, andas_aniframe()andaniframe()take anindexargument. A frame has exactly one index, and it is never a grouping variable.set_unit_time(),set_sampling_rate()andto_anievent()act on the declared index rather than a column namedtime, andvalidate_aniframe()checks it is present and numeric.An
anieventhas no index, since a bout is delimited bystartandstop. Itsvariables_indexisNAandget_index()errors on it. -
get_sampling_interval()reports the spacing of the index, derived from the data at construction rather than declared, andis_sampling_regular()says whether that spacing is even (#114). Nothing in the stack could previously tell whether a frame was regularly sampled, which several downstream functions need — interpolating on row position rather than on time is only correct when it is.Measured per key, since the index restarts in each group and a frame regular within every track can look irregular pooled. Regularity is computed on demand rather than stored, because dropping rows changes the answer; its
toleranceis an argument, and relative, so floating-point timestamps are not called irregular over the last decimal place.validate_aniframe()warns when a declaredsampling_ratedisagrees with the measured spacing — the same shape as #98, where the metadata claimed a unit the data was not in. get_sampling_rate(),get_unit_space(),get_unit_time()andget_unit_angle()read the fields that already had setters (#121). Every field with a dedicated setter now has a dedicated getter, so reading one no longer means naming it as a string. The factor-backed ones return a bare character vector, which is what callers were doing withas.character()anyway.get_coordinate_system()reads the coordinate system a frame is in (#109). There is deliberately no setter: the field is derived from the axis roles, soset_axes()says what the columns mean andanispace’smap_to_*()functions convert the coordinates.An
axesmetadata field records which column carries which axis role, so coordinates may be carried by columns of any name (#109).get_axes()reads it,set_axes()changes it, andas_aniframe()andset_variables_where()accept the same mapping —c(x = "u", y = "v").coordinate_systemfollows from it, andset_unit_space()and the axis extents resolve through it. The roles are a closed set:x,y,z,rho,phi,theta, and one that forms no coordinate system is rejected by name at declaration. Declaring spatial columns without roles keeps its old meaning, the column name being the role.Axis directions and extents record how a frame is laid out, replacing
originandy_height(#124).axis_directionsmaps each axis role to one ofright,left,up,down,backorforward, read from where the recording was made;axis_extentsmaps each to how far it runs.get_axis_directions(),set_axis_directions(),get_axis_extents()andset_axis_extents()read and write them. Turning an axis to its opposite reflects that column: around the axis extent where one is declared, around zero where none is. The column is found by role, so a frame whose vertical axis is called something else is handled, and an angular frame refuses rather than leaving every stored angle facing the wrong way.get_angle_direction()says which way angles run, derived from the axis directions rather than recorded (#124).atan2(y, x)counts counter-clockwise, so the same physical heading comes out mirrored between a y-down and a y-up frame; nothing said which convention a number was in.get_handedness()andset_handedness()say whether a frame is right- or left-handed (#124). Three declared axis directions determine it and are read in preference to thehandednessfield, which carries the convention for a frame that states one without spelling the axes out.set_handedness()defaults to right-handed and completes the third axis when two are declared.set_angle_direction()asks for a sense of rotation and declares the axis directions that give it (#124), turning the vertical axis over when both are already declared.wrap_angle()andunwrap_angle()move here fromanispace(#128). They are angle arithmetic rather than coordinate transformation, and belong besidedeg_to_rad()andrad_to_deg(), which were already here.animetricre-exported both fromanispace, which is the sign of a primitive sitting one layer above the packages that need it.Turning an axis over on a frame that stores angles recomputes them, rather than refusing (#134). No column carries
x,yorzon a polar, cylindrical or spherical frame, but the angles are measured from those axes: turningxover takes the supplement ofphi, turningyover negates it, and turningzover takes the supplement oftheta. The result comes back in the unit and range the frame keeps its angles in, andrhonever moves. An axis with a declared extent still refuses, because reflecting around it would move every point’s distance from the origin.Every exported function now has a runnable example (#106).
Changed
Declaring an axis role that is carried by one column while a different, undeclared column has that role’s name now warns (#119). The frame is legal and the mapping is right, but
.data$xthen returns a column that is not the x axis. Silence it withoptions(aniframe.quiet = TRUE).coordinate_systemfollows from which axis roles are declared rather than from column names (#109). A frame whose coordinates are named something else is now inferred correctly, where it degraded tounknownand was refused by every spatial function.is_cartesian(),is_polar(),is_cylindrical(),is_spherical(), theis_cartesian_*d()variants and theirensure_guards readcoordinate_systemrather than matching column names, and require an aniframe (#107, #109). A frame whose coordinates are called something else now satisfies the predicate for the system it is in, and an undeclared column no longer decides the answer — a spherical frame that has droppedrhofrom its declaration is no longer reported as spherical. The guards say which system the frame is in and how to get to the one you need.add_variables_where()andremove_variables_where()carry the axis roles through (#109). They combined bare column names, so on a frame with declared roles every addition or removal reduced it tounknown. Removing an axis until the remainder forms no coordinate system warns rather than aborting; declaring such a set outright still aborts.validate_aniframe()warns when identity, temporal context and the index together do not name one observation per row (#49). A repeat means some variable that tells the rows apart is undeclared, and every grouped operation folds them together.variables_whenno longer names the column the frame is indexed by; read that fromvariables_index, viaget_index()(#109). It holds only the temporal context, so a frame with none hascharacter()where it hadc("time"), as doeslist_default_metadata().aniprocess::filter_across()andfilter_na_across()takevariables_when[1]as the time column and must swap toaniframe::get_index(). Code reading the grouping columns is unaffected, and no longer has anything to exclude: they arec(variables_what, variables_when).default_metadata()is renamedlist_default_metadata()(#121).originandy_heightare removed, along withset_origin()andset_y_height()(#124). Useset_axis_directions()andset_axis_extents().originrecorded a corner, but the origin is(0, 0)in both of its values — what differed was the direction y increases in, which is what is recorded now. It also had nothing to say for 3D data or for a recording with no frame corners at all. The deprecatedpoint_of_referencealias goes with the field it aliased.as_aniframe()no longer fills in an axis extent from the data (#124).y_heightfell back tomax(y), which is the highest tracked point rather than the frame height, so a frame that was never told its height reflected around the wrong place. A frame now declares no extent until given one, and an axis with no extent is negated when turned over rather than reflected around a guess.aniread’s readers supply the video height they know about.spec_versionmoves toaniframe = "2.0.0"andanievent = "0.3.0"(#109). Major foraniframe:variables_whenno longer names the index, which breaks a consumer reading it from there. Minor foranievent, which gainsvariables_indexasNA.
Fixed
set_unit_space()converts the axis extents along with the coordinates (#124). An extent is a length, so converting cm to m left the frame claiming a height in the unit it no longer used.set_unit_space()converts the length axes of the frame’s coordinate system rather than whichever ofx,yandzare present (#98).rhois a length on polar, cylindrical and spherical frames and was never converted, while the metadata was updated to claim the new unit. Angular axes remainset_unit_angle()’s to convert. Where the coordinate system isunknowna length cannot be told from an angle, and the function now warns rather than silently converting nothing.set_unit_space(),set_unit_angle(),set_unit_time()andset_sampling_rate()no longer re-inject akeypointcolumn and overwritevariables_whatwith it (#96). A frame given a custom identity such asidwas silently regrouped on a constant column.as_aniframe()keeps the roles a frame already declares rather than re-deriving them, so casting an aniframe is no longer destructive (#96). A declaration whose columns have since been dropped still falls through to detection, so a cast continues to repair a drifted frame.