knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
The DetectorChecker R
package offers a quality check of digital X-ray detectors based on spatial analysis of damaged pixel distributions. It can also be used for analysing other types of defects on other types of instruments, as long as the data is similar. For example, instead of damaged pixels, the spatial arrangement of other types of dysfunctional pixels could be analysed. For an example of a different
type of instrument, consider CMOS sensors as used in digital cameras.
Here is how to load the package.
library(detectorchecker)
The elementary units (e.g. pixels) have to be arranged in a rectangular grid. The grid may be divided into rectangular modules, which may be separated by gaps. There are technical reasons for such gaps, and they are excluded from the analysis.
Several predefined detector layouts are already included in the package. Pilatus
, Excalibur
and PerkinElmerFull
(2000 by 2000 pixels) are common types of detectors built into professional X-ray machines. PerkinElmerCropped1600
and PerkinElmerRefurbished
are modified version of PerkinElmerFull
; they were created by restricting the area.
A list with all available detector layouts can be displayed.
available_detectors
If your data has been created using one of the predefined layouts then it suffices to upload the relevant detector layout. Otherwise a user-defined layout can be specified by reading in an appropriate text file as explained further below.
The first step is to create an R
object with the layout matching your data set.
Here are example invocations for Pilatus
and PerkinElmer_Full
.
detector_pilatus <- create_detector("Pilatus") detector_perkinfull <- create_detector("PerkinElmerFull")
A summary of the characteristics of the detector layout can be printed.
summary(detector_pilatus)
You can check if an object is an instance of the detector
with
is.detector(detector_pilatus)
We will now explain the attributes of this object using Pilatus
and PerkinElmerFull
as examples.
The first four attributes contain the name, a date (optional; use NA
if there is no need to specify this),
the width (number of pixels horizontally) and the height (numbers of pixels vertically) of the detector.
paste("name:", detector_pilatus$name) paste("date:", detector_pilatus$date) paste("width:", detector_pilatus$detector_width) paste("height", detector_pilatus$detector_height)
The grid layout is given by the following attributes The number of modules per row is given by the module_row_n attribute and per column in module_col_n.
paste("module_col_n:", detector_pilatus$module_col_n) paste("module_row_n:", detector_pilatus$module_row_n)
The sizes of the modules are stored in the next two attributes. module_col_sizes
contains the widths (numbers of pixel columns in each module), while module_row_sizes
contains the heights (numbers of pixel rows in each module). In the Pilatus
detector all modules are of the same dimensions. In the PerkinElmerFull
the first and last module of each row have fewer pixels.
print("Pilatus: Cols and Rows") detector_pilatus$module_col_sizes detector_pilatus$module_row_sizes print("Perkinfull: Cols and Rows") detector_perkinfull$module_col_sizes detector_perkinfull$module_row_size
The next two attributes contain the sizes of the gaps. The numbers of pixel columns per gap are in gap_col_sizes
, while the numbers of pixels rows per gap are in gap_row_sizes
In the Pilatus
detectors all horizontal gaps are of the same size, and all vertical gaps are of the same size. The various Perkin Elmer detectors and the Excalibur
detector have no gaps at all. However, it is possible to create layouts with more irregular gap sizes (see the comments below on user-defined layouts).
print("Pilatus: Gap Cols and Rows") detector_pilatus$gap_col_sizes detector_pilatus$gap_row_sizes print("Perkinfull: Gap Cols and Rows") detector_perkinfull$gap_col_sizes detector_perkinfull$gap_row_sizes
The final attributes are calculated from the other slots; they contain the left and the right edge (column numbers) and the top and bottom edge (row numbers) of each module. If there are no gaps, such as in the three Perkin Elmer layouts, then the left edge of a module will be adjacent to the right edge of the previous one in the same row of the grid and the bottom edge of a module will be adjacent to the top edge of the previous one.
print("Pilatus: Gap Cols and Rows") detector_pilatus$module_edges_col detector_pilatus$module_edges_row print("Perkinful: Gap Cols and Rows") detector_perkinfull$module_edges_col detector_perkinfull$module_edges_row
The final attribute detector_inconsistency
is a consistency check. If the sizes of the modules add up to the correct numbers, the status is 0. Otherwise, it is 1 and a suitable error message will be produced. The remaining slots will be filled when the detector damage information is read in.
detector_pilatus$detector_inconsistency
If your detector's layout is not contained in the list then you can create and read in the relevant layout parameters. You need to create a textfile that contains this information in a simple list setting all relevant parameters and store it in a directory of your choice.
We illustrate this by two examples. The first example is a simple layout for a detector with photographic aspect ratio with no subdivisions. We stored the lines below as a plain text file under the name layout_par_photographic.txt
in the subdirectory
extdata/user-defined/photographic/
of the detectorchecker
package.
detector_width = 600 detector_height = 400 module_col_n = 1 module_row_n = 1 module_col_sizes = c(600) module_row_sizes = c(400) gap_col_sizes = c() gap_row_sizes = c() module_edges_col = NA module_edges_row = NA
The second example is an irregular layout with varying module dimensions and varying gap sizes. We stored the lines below as a plain text file under the name layout_par_irregular.txt
in the subdirectory
extdata/user-defined/irregular/
of the detectorchecker
package.
detector_width = 1720 detector_height = 1060 module_col_n = 7 module_row_n = 5 module_col_sizes = c(100, 200, 300, 400, 300, 200, 100) module_row_sizes = c(100, 200, 400, 200, 100) gap_col_sizes = c(10,20,30,30,20,10) gap_row_sizes = c(10,20,20,10) module_edges_col = NA module_edges_row = NA
To generate a detector object from user-defined parameters, we use a function reading the parameters from the text file as shown below for our examples. For your own user-defined detector you need to set the variable file_path
to point to the directory where you stored the parameter file.
file_path <- system.file("extdata", "user-defined", "photographic", "layout_par_photographic.txt", package ="detectorchecker") user_def_detector_photographic <- readin_detector(file_path) file_path <- system.file("extdata", "user-defined", "irregular", "layout_par_irregular.txt", package ="detectorchecker") user_def_detector_irregular <- readin_detector(file_path)
A visualisation of the layout shows modules and gaps as shown below for two of the pre-specified layouts. The default is to plot a title, but we can override this by setting the parameter caption to FALSE
. The same rule applies for all plots produced using this package.
plot(detector_pilatus) plot(detector_pilatus, caption=FALSE) plot(detector_perkinfull)
This also works for the two user-defined detectors.
plot(user_def_detector_photographic) plot(user_def_detector_irregular)
The class for detectors considered admissible for this package is illustrated by Figure \ref{fig:detector_layout}
The next step is to upload a file containing information about damaged pixel locations. The format depends on what is provided for the detector type.
TIF
formatFor Pilatus
, the damaged pixels locations are usually provided as bad pixel masks in the form of tif
files. A sample file with raw damaged pixel mask data is provided with the package and can be loaded by specifying the relevant folder location (here extdata
) and file name (here Pilatus/badpixel_mask.tif
).
detector_pilatus <- create_detector("Pilatus") file_path <- system.file("extdata", "Pilatus", "badpixel_mask.tif", package ="detectorchecker") detector_pilatus <- load_pix_matrix( detector = detector_pilatus, file_path = file_path)
The spatial locations of the damaged pixels can be visualised.
plot_pixels(detector = detector_pilatus)
Individual modules can be plotted separately. Here we plot the module located in the 4^th^ column and 5^th^ row of the module layout grid.
plot_pixels(detector = detector_pilatus, col = 4, row = 5)
For the user-defined layout used above we can also display damaged pixels. If the location are stored in the form of a tif
file then this looks as follows.
file_path <- system.file("extdata", "user-defined", "photographic", "photographic.tif", package ="detectorchecker") user_def_detector_photographic <- load_pix_matrix( detector = user_def_detector_photographic, file_path = file_path) plot_pixels(detector = user_def_detector_photographic, caption = TRUE)
XML
formatFor the three Perkin Elmer layouts (PerkinElmerFull
, PerkinCropped1600
, and PerkinElmerRefurbished
) the damaged pixels locations are usually stored as xml
files. Sample files for each of the three Perkin Elmer layouts are supplied with the package and can be loaded by specifying the location and file name.
detector_perkinfull <- create_detector("PerkinElmerFull") file_path <- system.file("extdata", "PerkinElmerFull", "BadPixelMap_t1.bpm.xml", package = "detectorchecker") detector_perkinfull <- load_pix_matrix( detector = detector_perkinfull, file_path = file_path) plot_pixels(detector = detector_perkinfull) plot_pixels(detector = detector_perkinfull, col = 11, row = 1)
To plot another of the Perkin Elmer
layouts the detector type needs to be changed. For the refurbished detector layout this is done as follows:
To plot another of the Perkin Elmer layouts the detector type needs to be changed. For the refurbished detector layout this is done as follows:
detector_perkinrefurb <- create_detector("PerkinElmerRefurbished") file_path <- system.file("extdata", "PerkinElmerRefurbished", "BadPixelMap_t1.bpm.xml", package = "detectorchecker") detector_perkinrefurb <- load_pix_matrix( detector = detector_perkinrefurb, file_path = file_path) plot_pixels(detector = detector_perkinrefurb)
To see the other sample data sets, change the file name, e.g.:
detector_perkinrefurb <- create_detector("PerkinElmerRefurbished") file_path <- system.file("extdata", "PerkinElmerRefurbished", "BadPixelMap_t2.bpm.xml", package = "detectorchecker") detector_perkinrefurb <- load_pix_matrix( detector = detector_perkinrefurb, file_path = file_path) plot_pixels(detector = detector_perkinrefurb)
This also works for a user-defined detector if the damaged pixel locations are stored in xml
format. (Dead pixels locations in the example file were generated by simulating a Poisson point pattern with inhomogeneous density.)
file_path <- system.file("extdata", "user-defined", "photographic", "badpixelmap_photographic_inhom.xml", package ="detectorchecker") user_def_detector_photographic <- load_pix_matrix( detector = user_def_detector_photographic, file_path = file_path) plot_pixels(detector = user_def_detector_photographic, caption = TRUE)
For the second user-defined example, we proceed similarly.
file_path <- system.file("extdata", "user-defined", "irregular", "badpixelmap_irregular.xml", package ="detectorchecker") user_def_detector_irregular <- load_pix_matrix( detector = user_def_detector_irregular, file_path = file_path) plot_pixels(detector = user_def_detector_irregular)
HDF
formatFor Excalibur
, the damaged pixels locations are usually provided in a combination of 6 hdf
files corresponding to the layout rows. One such collection of sample files is provided with the package.
detector_exc <- create_detector("Excalibur") file_path <- c( system.file("extdata", "Excalibur", "pixelmask.fem1.hdf", package ="detectorchecker"), system.file("extdata", "Excalibur", "pixelmask.fem2.hdf", package ="detectorchecker"), system.file("extdata", "Excalibur", "pixelmask.fem3.hdf", package ="detectorchecker"), system.file("extdata", "Excalibur", "pixelmask.fem4.hdf", package ="detectorchecker"), system.file("extdata", "Excalibur", "pixelmask.fem5.hdf", package ="detectorchecker"), system.file("extdata", "Excalibur", "pixelmask.fem6.hdf", package ="detectorchecker") ) detector_exc <- load_pix_matrix( detector = detector_exc, file_path = file_path) plot_pixels(detector = detector_exc)
As for the other layout types, individual modules can be plotted.
plot_pixels(detector = detector_exc, col = 6, row = 4)
Approximate damage intensity can be visualized by plotting a density. The parameter adjust
must be positive and can be varied to tune the smoothness of the density estimation procedure.
plot_pixels_density(detector = detector_pilatus) plot_pixels_density(detector = detector_perkinfull) plot_pixels_density(detector = user_def_detector_photographic)
The same plot can be carried out for individual modules. In the case of a density map, the smoothing chosen for the sub-panel is different from that chosen for the plot for the entire panel, so it will not simply be a detail of the larger image.
plot_pixels_density(detector = detector_pilatus, row = 5, col = 1)
The parameter adjust
influences the bandwidth used in the kernel based density estimation. The default is set at 0.5. Smaller values will emphasise the role of individual pixels, while larger values result in smoothing over larger areas.
plot_pixels_density(detector = detector_pilatus, adjust=0.1) plot_pixels_density(detector = detector_pilatus, adjust=2)
A calculation of the total number of damaged pixels, the average number of damaged pixels per module, and a Chi-square test for its independence of the module can be performed. We show this here for Pilatus
, preceded by a summary of the relevant layout characteristics.
summary(detector_pilatus) cat(dead_stats_summary(detector_pilatus))
A systematic display of the counts of damaged pixels per module can be displayed for the whole detector or for an individual module.
detector_pilatus <- get_dead_stats(detector_pilatus) plot_pixels_count(detector = detector_pilatus) plot_pixels_count(detector = detector_pilatus, row = 5, col = 12)
For each damaged pixel, a nearest neighbour can be determined and all such relationships can be visualised by plotting directed arrows. Specifying coordinates in the layout grid plots only the corresponding modules. We illustrate this for one of the Perkin Elmer data examples.
plot_pixels_arrows(detector = detector_perkinfull) plot_pixels_arrows(detector = detector_perkinfull, row = 1, col = 1)
Angles between neighbours can be summarised in a rose diagram.
plot_pixels_angles(detector = detector_pilatus)
In some modules, the rose diagram is dominated by vertical and horizontal directions. However, in other modules this is not the case.
plot_pixels_angles(detector = detector_pilatus, row = 5, col = 11) plot_pixels_angles(detector = detector_pilatus, row = 5, col = 4)
A number of distance-based functions can be used to check whether
the point pattern created by the damaged pixels has
features that would be expected from a pattern exhibiting complete spatial randomness (CSR) [@Chiu2013]: Ripley's $K$ function, the empty space function $F$ and the nearest-neighbour function $G$. Scales of the axes are chosen by spatstat
default.
F function or “empty space function” computes the distribution of the nearest distance to a defect point from a typical location chosen uniformly from the image rectangle; if the point pattern did in fact satisfy CSR then an empirical estimate of the F function could be viewed as a random perturbation of the theoretical F function under CSR.
The G function computes the distribution of nearest-neighbour distances between defect points; if the point pattern did in fact satisfy CSR then an empirical estimate of the G function could be viewed as a random perturbation of the theoretical G function under CSR.
The K function (Ripley’s K function) computes the mean number of defect points within a distance r of a typical defect point, viewed as a function of r; if the point pattern did in fact satisfy CSR then an empirical estimate of the K function could be viewed as a random perturbation of the theoretical K function under CSR.
The basic versions of these functions assume a homogeneous background density.
plot_pixels_kfg(detector = detector_pilatus, func = "K") plot_pixels_kfg(detector = detector_pilatus, func = "F") plot_pixels_kfg(detector = detector_pilatus, func = "G")
Individual modules can be selected as before. The functions consistently show a deviation from what would be expected from complete spatial randomness (which is graphed as K_pois
). Note that there are several different empirical variants of each function, corresponding to different edge-correction techniques.
plot_pixels_kfg(detector = detector_pilatus, func = "K", row = 5, col = 12) plot_pixels_kfg(detector = detector_pilatus, func = "F", row = 5, col = 12) plot_pixels_kfg(detector = detector_pilatus, func = "G", row = 5, col = 12)
There are also versions of these functions that allow for an inhomogeneous background density (itself estimated from the data).
plot_pixels_kfg(detector = detector_pilatus, func = "Kinhom") plot_pixels_kfg(detector = detector_pilatus, func = "Finhom") plot_pixels_kfg(detector = detector_pilatus, func = "Ginhom")
As is the case for their homogeneous counterparts, they can also be computed for individual modules.
plot_pixels_kfg(detector = detector_pilatus, func = "Kinhom", row = 5, col = 12) plot_pixels_kfg(detector = detector_pilatus, func = "Finhom", row = 5, col = 12) plot_pixels_kfg(detector = detector_pilatus, func = "Ginhom", row = 5, col = 12)
Instead of interpreting every damaged pixel by itself, small clusters and lines can be interpreted as one damage event. Create a new object by specifying which kinds of small ensembles should be summarised as damage events (1=singletons, 2=doublets, 3=triplets, 4=larger clusters, 5=upper horizontal lines, 6=lower horizontal lines, 7=left vertical lines, 8=right vertical lines). To include all kinds of ensembles list them all.
The differences between pixel and event level are demonstrated well e.g. in the PerkinElmerFull
example data sets, because they possess lines of damaged pixels.
incl_event_list <- list(1,2,3,4,5,6,7,8) detector_perkinfull_events <- detectorchecker::find_clumps(detector_perkinfull)
For the Perkin Elmer damaged pixel example files, there is an obvious difference between the pixel level (first plot below) and the event level (second plot below).
plot_pixels(detector_perkinfull) detectorchecker::plot_events(detector_perkinfull_events, incl_event_list = incl_event_list)
An individual module can be plotted by referring to the corresponding row and column.
plot_pixels(detector_perkinfull, row = 1, col = 11) plot_events(detector_perkinfull_events, incl_event_list = incl_event_list, row = 1, col = 11)
Much of the analysis carried out on the pixel level can also be conducted on the event level.
A graphical display of the counts of damage events per module can be displayed for the whole detector or a module.
detectorchecker::plot_events_count(detector_perkinfull_events, incl_event_list = incl_event_list)
For density plots the optional adjust parameter can be omitted (default=0.5) or changed to another positive value. Individual modules can be selected.
detectorchecker::plot_events_density(detector_perkinfull_events, incl_event_list = incl_event_list) detectorchecker::plot_events_density(detector_perkinfull_events, incl_event_list = incl_event_list, row=1, col=16, adjust=1)
For the complete detector or for individual modules:
detectorchecker::plot_events_arrows(detector_perkinfull_events, incl_event_list = incl_event_list) detectorchecker::plot_events_arrows(detector_perkinfull_events, incl_event_list = incl_event_list, row=1, col=16)
For the complete detector or for individual modules:
detectorchecker::plot_events_angles(detector_perkinfull_events, incl_event_list = incl_event_list) detectorchecker::plot_events_angles(detector_perkinfull_events, incl_event_list = incl_event_list, row=1, col=16)
This can be conducted in the same way as for the pixel level. It is worth comparing the results on the pixel level with those on the event level, because results may be different. We show below a number of ways to do this for the $K$ function. Similar approaches work for $F$ and $G$ functions.
detectorchecker::plot_events_kfg(detector_perkinfull_events, func = "K", incl_event_list = incl_event_list) detectorchecker::plot_events_kfg(detector_perkinfull_events, func = "K", incl_event_list = incl_event_list, row=1, col=16) detectorchecker::plot_events_kfg(detector_perkinfull_events, func = "Kinhom", incl_event_list = incl_event_list)
In some situations, the analysis may be dominated by an area of elevated damage. The investigation of complete spatial randomness then becomes uninformative. The area with elevated damage can be removed.
detector_perkinfull_modified <- remove_high_density_cluster( detector_perkinfull, min_pts = 30, eps_adjust = 0.05) plot_pixels(detector = detector_perkinfull_modified)
The analysis for complete spatial randomness can be conducted on the remaining area.
plot_pixels_kfg(detector = detector_perkinfull_modified, func = "K") plot_pixels_kfg(detector = detector_perkinfull_modified, func = "F") plot_pixels_kfg(detector = detector_perkinfull_modified, func = "G")
This section concerns inferential statistical methods to analyse the state (damaged or intact) of the pixels. General linear models can include a spatial covariate reflecting the location of a pixel relative to the centre, to the edges or the corners of the detector. The spatial covariate can also express the location relative to its containing module (sub-panel). This can be distance of the pixel to the closest vertical or horizontal edge of the containing module, or the minimum of both of these (i.e. the distance to the closest edge of the containg module). Running times can be slow, but should not take longer than a minute for detector types included in this package when using a notebook computer of normal specification (as of 2020).
We start with the spatial covariates that reflect the geometry of the pixels with respect to the detector as a whole. The following command fits a model that includes the euclidean distance of a pixel to the centre of the detector as covariate.
glm_pixel_ctr_eucl(detector_pilatus)
The next command includes the maximum of the vertical and horizontal distances of a pixel to the centre of the detector (also called $L^\infty$ or L-infinity distance).
glm_pixel_ctr_linf(detector_pilatus)
It is also possible to include the distances of a pixel to the nearest corner of the detector as a covariate.
glm_pixel_dist_corner(detector_pilatus)
Now we look at covariates that reflect the position of the pixel with respect to the containing module.
The function glm_pixel_dist_edge_col()
fits a model that includes the distance of the relevant pixel
from the nearest vertical edge of the containing module.
glm_pixel_dist_edge_col(detector_pilatus)
The function glm_pixel_dist_edge_row()
does the same for rows, and glm_pixel_dist_edge_min()
uses the smaller distance of the two.
All these commands are also available on the event level by using the same name but
substituting events
for pixel
in the command name.
For example, the following command fits a model that includes
as covariate the euclidean distance of an event to the centre.
glm_events_ctr_eucl(detector_pilatus)
For illustrative purposes it is also possible to visualise the spatial covariates. (This step would typically be missed out, since the plotting process is quite slow.)
The function plot_pixel_ctr_eucl()
displays euclidean distance of pixels
to the centre of the detector using a colour scheme.
The function plot_pixel_ctr_linf()
displays the maximum of the vertical and horizontal distances of a pixel to the centre of the detector (also called $L^\infty$ or L-infinity distance).
The next command displays euclidean distance of pixels to the nearest corner.
plot_pixel_dist_corner(detector_pilatus)
The function plot_pixel_dist_edge_col()
displays distance of pixels from the nearest vertical module edge, while plot_pixel_dist_edge_row()
does the same for horizontal boundaries.
In conclusion, the following command is based on
the minimum of the distances displayed by plot_pixel_dist_edge_col()
and plot_pixel_dist_edge_row()
;
in other words, it displays the distance of pixels to nearest module edge.
plot_pixel_dist_edge(detector_pilatus)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.