library(cmcR)
library(magrittr)
library(ggplot2)
library(purrr)
library(dplyr)
library(tidyr)
library(x3ptools)
library(rgl)
For the sake of an example we will consider 3 cartridge cases are from Fadul et al. (2011). The data are openly available from the NIST Ballistics Toolmark Research Database. Specifically, we will consider the comparisons between “Fadul 1-1” and “Fadul 1-2” (available here) and “Fadul 2-1” (available here). Note that Fadul 1-1 and Fadul 1-2 are known to have been fired from the same firearm while Fadul 2-1 was fired from a different firearm.
The rgl
package can be used to visualize the cartridge case scans in 3 dimensions. The code below shows how to construct an rgl
“scene” including placing lights at desired locations and controlling color, surface reflectance, etc. Each rgl
scene can support up to 8 lights, but two are setup in the scene below - one directly above the scan at \((0,0,10)\) and another located “north-by-northwest” of the scan at \((-1,2,2)\). Note that these positions are relative to where the viewer begins - which is somewhere along the \(z\)-axis depending on the value provided to the zoom
attribute. Based on experimentation, it appears that applying a Gaussian filter to the surface matrix prior to setting up the scene makes the breech face impressions more visually distinct (otherwise, a good deal of noise due to the scanning process makes it difficult to see much structure). There’s truly an arbitrary amount of tinkering one could perform to get the “best” looking scene for a particular cartridge case scan, so in no way is the scene setup shown below generalizable to more cartridge case scans. Ince the rgl
device is opened, the x3ptools::x3p_snapshot
function saves the device’s current viewpoint as a .png in a local directly called bfScanImages (Note: as of 11/22/20, the x3p_snapshot
function is only available in the development version of x3ptools
). The same procedure is carried out for the fadul1.2_raw
object (not shown).
To learn more about how various aspects of a scene are constructed, see the vignettes for the rgl
package. For more details on using rgl
specifically applied to .x3p files, see the documentation for the x3ptools
package on Github.
fadul1.1_id <- "DownloadMeasurement/2d9cc51f-6f66-40a0-973a-a9292dbee36d"
# Same source comparison
fadul1.2_id <- "DownloadMeasurement/cb296c98-39f5-46eb-abff-320a2f5568e8"
# Code to download breech face impressions:
nbtrd_url <- "https://tsapps.nist.gov/NRBTD/Studies/CartridgeMeasurement"
fadul1.1_raw <- read_x3p(file.path(nbtrd_url,fadul1.1_id))
fadul1.2_raw <- read_x3p(file.path(nbtrd_url,fadul1.2_id))
#apply lowpass filter to reduce noise in scan:
surface1 <- fadul1.1_raw %>%
cmcR::preProcess_gaussFilter(wavelength = 16,filtertype = "lp")
surface2 <- fadul1.2_raw %>%
cmcR::preProcess_gaussFilter(wavelength = 16,filtertype = "lp")
params <- rgl::r3dDefaults
zoom = .7
size = c(300,300)
params$windowRect <- c(40, 125, 40 + size[1], 125 + size[2])
params$userMatrix <- diag(c(1, 1, 1, 1))
params$zoom <- zoom
#opens blank "canvas" upon which we can add lights, surfaces, etc.
open3d(params = params)
#removes any previously declared lights in scene
rgl.pop("lights")
#set-up two lights for scene -- a lot of experimentation possible here
light3d(x = -1,y = 1,z = 2,viewpoint.rel = TRUE,ambient = "white",diffuse = "white",specular = "white")
light3d(x = 0,y = 0,z = 10,ambient = "grey60",diffuse = "grey50",specular = "grey60",viewpoint.rel = TRUE)
#setup surface visualization
multiply <- 1 #x3ptools::image_x3p default to exaggerate relief
z <- multiply * surface1$surface.matrix # Exaggerate the relief
yidx <- ncol(z):1
y <- fadul1.1_raw$header.info$incrementY * yidx
x <- fadul1.1_raw$header.info$incrementX * (1:nrow(z))
# emission, specular, ambient affect how the surface interacts with lights --
# again, a lot of possible experimentation
surface3d(x, y, z, back = "filled",emission = "grey30",specular = "grey50",ambient = "grey10")
x3ptools::x3p_snapshot(file = "bfScanImages/fadul1-1.png")
rgl.close()
plt <- magick::image_append(c(magick::image_read("bfScanImages/fadul1-1.png"),
magick::image_read("bfScanImages/fadul1-2.png")))
# magick::image_write(path = "unprocessedScans.png",image = plt)
Plotting the cartridge case surface matrix in which height values are mapped to a divergent color scheme may help highlight breech face impressions. We can use ggplot2
to accomplish this.
data("fadul1.1_processed","fadul1.2_processed")
#Download a non-matching cartridge case to Fadul 1-1 and Fadul 1-2
fadul2.1_raw <- x3ptools::read_x3p("https://tsapps.nist.gov/NRBTD/Studies/CartridgeMeasurement/DownloadMeasurement/8ae0b86d-210a-41fd-ad75-8212f9522f96")
fadul2.1_processed <- fadul2.1_raw %>%
preProcess_crop(region = "exterior",
radiusOffset = -30) %>%
preProcess_crop(region = "interior",
radiusOffset = 200) %>%
preProcess_removeTrend(statistic = "quantile",
tau = .5,
method = "fn") %>%
preProcess_gaussFilter() %>%
x3ptools::sample_x3p()
plt <- cmcR::x3pListPlot(x3pList = list("Fadul 1-1" = fadul1.1_processed,
"Fadul 1-2" = fadul1.2_processed,
"Fadul 2-1" = fadul2.1_processed),
type = "faceted",
rotate = 90,
legend.quantiles = c(0,.01,.2,.5,.8,.99,1))
# ggsave("processedScans.png",plot = plt)
The code below will perform the cell-based comparison procedure and then determine the initial/high CMCs for the known match and known non-match pairs of cartridge cases. The “initial” CMCs are determined based on the originally proposed method from Song (2013) while the “high” CMCs are determined using the “High CMC” method proposed by Tong et al. (2015).
kmComparisonFeatures <- purrr::map_dfr(seq(-30,30,by = 3),
~ comparison_allTogether(reference = fadul1.1_processed,
target = fadul1.2_processed,
theta = .))
kmComparisonFeatures_rev <- purrr::map_dfr(seq(-30,30,by = 3),
~ comparison_allTogether(reference = fadul1.2_processed,
target = fadul1.1_processed,
theta = .))
kmComparison_allCMCs <- kmComparisonFeatures %>%
mutate(originalMethodClassif = decision_CMC(cellIndex = cellIndex,
x = x,
y = y,
theta = theta,
corr = pairwiseCompCor,
xThresh = 20,
thetaThresh = 6,
corrThresh = .5),
highCMCClassif = decision_CMC(cellIndex = cellIndex,
x = x,
y = y,
theta = theta,
corr = pairwiseCompCor,
xThresh = 20,
thetaThresh = 6,
corrThresh = .5,
tau = 1))
kmComparison_allCMCs_rev <- kmComparisonFeatures_rev %>%
mutate(originalMethodClassif = decision_CMC(cellIndex = cellIndex,
x = x,
y = y,
theta = theta,
corr = pairwiseCompCor,
xThresh = 20,
thetaThresh = 6,
corrThresh = .5),
highCMCClassif = decision_CMC(cellIndex = cellIndex,
x = x,
y = y,
theta = theta,
corr = pairwiseCompCor,
xThresh = 20,
thetaThresh = 6,
corrThresh = .5,
tau = 1))
knmComparisonFeatures <- purrr::map_dfr(seq(-30,30,by = 3),
~ comparison_allTogether(reference = fadul1.1_processed,
target = fadul2.1_processed,
theta = .))
knmComparisonFeatures_rev <- purrr::map_dfr(seq(-30,30,by = 3),
~ comparison_allTogether(reference = fadul2.1_processed,
target = fadul1.1_processed,
theta = .))
knmComparison_allCMCs <- knmComparisonFeatures %>%
mutate(originalMethodClassif = decision_CMC(cellIndex = cellIndex,
x = x,
y = y,
theta = theta,
corr = pairwiseCompCor,
xThresh = 20,
thetaThresh = 6,
corrThresh = .5),
highCMCClassif = decision_CMC(cellIndex = cellIndex,
x = x,
y = y,
theta = theta,
corr = pairwiseCompCor,
xThresh = 20,
thetaThresh = 6,
corrThresh = .5,
tau = 1))
knmComparison_allCMCs_rev <- knmComparisonFeatures_rev %>%
mutate(originalMethodClassif = decision_CMC(cellIndex = cellIndex,
x = x,
y = y,
theta = theta,
corr = pairwiseCompCor,
xThresh = 20,
thetaThresh = 6,
corrThresh = .5),
highCMCClassif = decision_CMC(cellIndex = cellIndex,
x = x,
y = y,
theta = theta,
corr = pairwiseCompCor,
xThresh = 20,
thetaThresh = 6,
corrThresh = .5,
tau = 1))
We can then visualize the CMCs for these comparisons. Below are the CMCs determined under the original method of Song (2013).
kmCMCPlot <- cmcR::cmcPlot(reference = fadul1.1_processed,
target = fadul1.2_processed,
reference_v_target_CMCs = kmComparison_allCMCs,
target_v_reference_CMCs = kmComparison_allCMCs_rev,
type = "faceted",
x3pNames = c("Fadul 1-1","Fadul 2-1"),
legend.quantiles = c(0,.01,.2,.5,.8,.99,1),
cell.colors = c("#a60b00","#1b03a3"),
cell.alpha = .15,
na.value = "gray80") %>%
map(~ . + theme(strip.text = element_blank()))
kmLegend_originalCMC <- cowplot::get_legend(kmCMCPlot$originalMethodCMCs_reference_v_target +
theme(legend.direction = "horizontal"))
km_originalCMC_reference_v_target <- kmCMCPlot$originalMethodCMCs_reference_v_target +
theme(legend.position = "none",
plot.margin=unit(c(-.05,-.5,-.05,-.5), "cm"),
plot.title = element_blank())
km_originalCMC_target_v_reference <- kmCMCPlot$originalMethodCMCs_target_v_reference +
theme(legend.position = "none",
plot.margin=unit(c(-.05,-.5,-.05,-.5), "cm"),
plot.title = element_blank())
km_originalCMCPlot_bothDirections <- ggplot(data.frame(a = 1)) +
theme_void() +
coord_cartesian(xlim = c(1,10),
ylim = c(1,11),
expand = FALSE) +
annotation_custom(ggplotGrob(km_originalCMC_reference_v_target),
xmin = 1,xmax = 10,ymin = 6.2,ymax = 11) +
annotation_custom(ggplotGrob(km_originalCMC_target_v_reference),
xmin = 1,xmax = 10,ymin = 2,ymax = 6.2) +
annotation_custom(kmLegend_originalCMC,
xmin = 1,xmax = 10,ymin = 1.45,ymax = 1.45) +
annotate("text",x = 3.85,y = 8.15,size = 5,label = "Fadul 1-1") +
annotate("text",x = 3.85,y = 4,size = 5,label = "Fadul 1-1") +
annotate("text",x = 7.05,y = 8.15,size = 5,label = "Fadul 1-2") +
annotate("text",x = 7.05,y = 4,size = 5,label = "Fadul 1-2")
# ggsave("kmOriginalmethodCMCs.png",km_originalCMCPlot_bothDirections)
And the CMCs determined under the High CMC method of Tong et al. (2015).
kmLegend_highCMC <- cowplot::get_legend(kmCMCPlot$highCMC_reference_v_target +
theme(legend.direction = "horizontal"))
km_highCMC_reference_v_target <- kmCMCPlot$highCMC_reference_v_target +
theme(legend.position = "none",
plot.margin=unit(c(-.05,-.5,-.05,-.5), "cm"),
plot.title = element_blank())
km_highCMC_target_v_reference <- kmCMCPlot$highCMC_target_v_reference +
theme(legend.position = "none",
plot.margin=unit(c(-.05,-.5,-.05,-.5), "cm"),
plot.title = element_blank())
km_highCMCCMCPlot_bothDirections <- ggplot(data.frame(a = 1)) +
theme_void() +
coord_cartesian(xlim = c(1,10),
ylim = c(1,11),
expand = FALSE) +
annotation_custom(ggplotGrob(km_highCMC_reference_v_target),
xmin = 1.1,xmax = 10,ymin = 6.55,ymax = 11) +
annotation_custom(ggplotGrob(km_highCMC_target_v_reference),
xmin = 1,xmax = 10,ymin = 2,ymax = 6.55) +
annotation_custom(kmLegend_highCMC,
xmin = 1,xmax = 10,ymin = 1.45,ymax = 1.45) +
annotate("text",x = 3.65,y = 8.65,size = 5,label = "Fadul 1-1") +
annotate("text",x = 3.65,y = 4,size = 5,label = "Fadul 1-1") +
annotate("text",x = 7.25,y = 8.65,size = 5,label = "Fadul 1-2") +
annotate("text",x = 7.25,y = 4,size = 5,label = "Fadul 1-2")
# ggsave("kmHighCMCs.png",km_highCMCCMCPlot_bothDirections)
The known non-match pair didn’t yield any high CMCs, so we can only visualize the CMCs identified under the original method of Song (2013).
knmCMCPlot <- cmcR::cmcPlot(reference = fadul1.1_processed,
target = fadul2.1_processed,
reference_v_target_CMCs = knmComparison_allCMCs,
target_v_reference_CMCs = knmComparison_allCMCs_rev,
type = "faceted",
x3pNames = c("Fadul 1-1","Fadul 2-1"),
legend.quantiles = c(0,.01,.2,.5,.8,.99,1),
cell.colors = c("#a60b00","#1b03a3"),
cell.alpha = .15,
na.value = "gray80") %>%
map(~ . + theme(strip.text = element_blank()))
knmLegend <- cowplot::get_legend(knmCMCPlot$originalMethodCMCs_reference_v_target +
theme(legend.direction = "horizontal"))
knm_reference_v_target <- knmCMCPlot$originalMethodCMCs_reference_v_target +
theme(legend.position = "none",
plot.margin=unit(c(-.05,-.5,-.05,-.5), "cm"),
plot.title = element_blank())
knm_target_v_reference <- knmCMCPlot$originalMethodCMCs_target_v_reference +
theme(legend.position = "none",
plot.margin=unit(c(-.05,-.5,-.05,-.5), "cm"),
plot.title = element_blank())
knm_cmcPlot_bothDirections <- ggplot(data.frame(a = 1)) +
theme_void() +
coord_cartesian(xlim = c(1,10),
ylim = c(1,11),
expand = FALSE) +
annotation_custom(ggplotGrob(knm_reference_v_target),
xmin = 1,xmax = 10,ymin = 6.5,ymax = 11) +
annotation_custom(ggplotGrob(knm_target_v_reference),
xmin = 1,xmax = 10,ymin = 2,ymax = 6.5) +
annotation_custom(knmLegend,
xmin = 1,xmax = 10,ymin = 1.45,ymax = 1.45) +
annotate("text",x = 3.75,y = 8.65,size = 5,label = "Fadul 1-1") +
annotate("text",x = 3.75,y = 4,size = 5,label = "Fadul 1-1") +
annotate("text",x = 7.45,y = 8.65,size = 5,label = "Fadul 2-1") +
annotate("text",x = 7.45,y = 4,size = 5,label = "Fadul 2-1")
# ggsave("knmOriginalMethodCMCs.png",knm_cmcPlot_bothDirections)
Song, J. (2013). Proposed “NIST Ballistics Identification System (NBIS)” Based on 3D Topogra-phy Measurements on Correlation Cells.American Firearm and Tool Mark Examiners Journal,45(2):11.
Tong, M., Song, J., and Chu, W. (2015). An Improved Algorithm of Congruent Matching Cells(CMC) Method for Firearm Evidence Identifications.Journal of Research of the National Institute of Standards and Technology, 120:102.
Fadul T., Hernandez G., Stoiloff S. and Gulati Sneh “An Empirical Study to Improve the Scientific Foundation of Forensic Firearm and Tool Mark Identification Utilizing 10 Consecutively Manufactured Slides,” 2011 NCJRS 237960