diff --git a/docs/src/api.md b/docs/src/api.md
index 2066fa70c9c75f48ab9fe811aa9e34f2b187838e..540cf7f3491a7ea3154dedc6f27fc172cb592b6c 100644
--- a/docs/src/api.md
+++ b/docs/src/api.md
@@ -59,6 +59,7 @@ modules
 getmodule
 getpmt
 haslocation
+hasstring
 isbasemodule
 isopticalmodule
 write(::AbstractString, ::Detector)
diff --git a/src/exports.jl b/src/exports.jl
index c039ca3045fae88076746f384ff05dab4c53b0b6..984ddd0549a70b49b02a13e2a2bb694249f130ba 100644
--- a/src/exports.jl
+++ b/src/exports.jl
@@ -29,6 +29,7 @@ getmodule,
 getpmt,
 getpmts,
 haslocation,
+hasstring,
 isbasemodule,
 isopticalmodule,
 modules,
diff --git a/src/hardware.jl b/src/hardware.jl
index 9e6901891a910bb1bd14a8d1b503db9e175684e6..6b5cf0f04554da932e283fd030461781ecd9c4a8 100644
--- a/src/hardware.jl
+++ b/src/hardware.jl
@@ -95,6 +95,8 @@ Get the PMT for a given DAQ channel ID (TDC)
 """
 getpmt(d::DetectorModule, channel_id::Integer) = d[channel_id]
 
+Base.filter(f::Function, d::DetectorModule) = filter(f, getpmts(d))
+
 """
 
 Calculate the centre of a module by fitting the crossing point of the PMT axes.
@@ -298,8 +300,16 @@ function Base.iterate(d::Detector, state=(Int[], 1))
     end
     (d.modules[module_ids[count]], (module_ids, count + 1))
 end
-Base.getindex(d::Detector, module_id::Integer) = d.modules[module_id]
-Base.getindex(d::Detector, string::Integer, floor::Integer) = d.locations[string, floor]
+function Base.getindex(d::Detector, module_id::Integer)
+    haskey(d.modules, module_id) && return d.modules[module_id]
+    error("Module with ID $(module_id) not found.")
+end
+function Base.getindex(d::Detector, string::Integer, floor::Integer)
+    haskey(d.locations, (string, floor)) && return d.locations[string, floor]
+    available_strings = join(d.strings, ", ", " and ")
+    !(hasstring(d, string)) && error("String $(string) not found. Available strings: $(available_strings).")
+    error("String $(string) has no module at floor $(floor).")
+end
 """
 Return the detector module for a given module ID.
 """
@@ -339,6 +349,8 @@ function Base.getindex(d::Detector, ::Colon, floors::UnitRange{T}) where T<:Inte
     sort!(modules)
 end
 
+Base.filter(f::Function, d::Detector) = filter(f, modules(d))
+
 """
 
 Returns true if there is a module at the given location.
@@ -346,6 +358,14 @@ Returns true if there is a module at the given location.
 """
 haslocation(d::Detector, loc::Location) = haskey(d.locations, (loc.string, loc.floor))
 
+"""
+
+Returns true if there is a string with a given number.
+
+"""
+hasstring(d::Detector, s::Integer) = s in d.strings
+
+
 """
 
 Calculate the center of the detector based on the location of the optical modules.
diff --git a/test/hardware.jl b/test/hardware.jl
index 66afa78a7e1cdf4bcedfcbb00aba9af7828b064f..7595fb55948297348ba3c11e95bf99950fa040d3 100644
--- a/test/hardware.jl
+++ b/test/hardware.jl
@@ -113,6 +113,13 @@ end
         @test 0 < length(det)
     end
 end
+@testset "DETX tools" begin
+    det = Detector(datapath("detx", "orca_115strings_av20min17mhorizontal_18OMs_alt9mvertical_v2.detx"))
+    @test haslocation(det, Location(1, 1))
+    @test !haslocation(det, Location(1, 100))
+    @test hasstring(det, 1)
+    @test !hasstring(det, 200)
+end
 @testset "DETX floor == -1 bug" begin
     det = Detector(datapath("detx", "orca_115strings_av20min17mhorizontal_18OMs_alt9mvertical_v2.detx"))
     @test Location(1, 1) == det[1].location