No reference tables could be found for input region & only part of the image can load the reference tables

Recently I come across a questions when processing the data:

camera.py of mine:

i = 4
for j in range(0,16):
    k = j + i*16
    config.detectorList[k] = lsst.afw.cameraGeom.cameraConfig.DetectorConfig()
    # y0 of pixel bounding box
    config.detectorList[k].bbox_y0 = 0
    
    # y1 of pixel bounding box
    config.detectorList[k].bbox_y1 = 4631
    
    # x1 of pixel bounding box
    config.detectorList[k].bbox_x1 = 1151
    
    # x0 of pixel bounding box
    config.detectorList[k].bbox_x0 = 0
    
    # Name of detector slot
    config.detectorList[k].name = f'4_16_{j}'
    
    # Pixel size in the x dimension in mm
    config.detectorList[k].pixelSize_x = 0.01
    
    # Name of native coordinate system
    config.detectorList[k].transformDict.nativeSys = 'Pixels'
    
    config.detectorList[k].transformDict.transforms = None
    

    # x position of the reference point in the detector in pixels in transposed coordinates.
    config.detectorList[k].refpos_x = 4607.5

    # y position of the reference point in the detector in pixels in transposed coordinates.
    config.detectorList[k].refpos_y = 4615.5


    config.detectorList[k].pixelSize_y = 0.01
    
    # Detector type: SCIENCE=0, FOCUS=1, GUIDER=2, WAVEFRONT=3
    config.detectorList[k].detectorType = 0
    if j//8 ==0:
        # x offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_x = 0 + 0.01*1152*(j%8)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 0 
    else:
        config.detectorList[k].offset_x = 0 + 0.01*1152*(j%8)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 0 + 0.01*4616

    config.detectorList[k].transposeDetector = False
    
    # yaw (rotation about z) of the detector in degrees. This includes any
    # necessary rotation to go from detector coordinates to camera coordinates
    # after optional transposition.
    config.detectorList[k].yawDeg = 0.0
    
    # roll (rotation about x) of the detector in degrees
    config.detectorList[k].rollDeg = 0.0
    
    # Serial string associated with this specific detector
    config.detectorList[k].serial = f'4_16_{j}'
    
    # pitch (rotation about y) of the detector in degrees
    config.detectorList[k].pitchDeg = 0.0
    
    # ID of detector slot
    config.detectorList[k].id = k

The code related to the location of amplifier is:

  if j//8 ==0:
        # x offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_x = 0 + 0.01*1152*(j%8)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 0 
    else:
        config.detectorList[k].offset_x = 0 + 0.01*1152*(j%8)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 0 + 0.01*4616

and things goes well for the amplifier in that (j%8 =0,1,2), but to a higher number, for example, I can process detector 64,65,66, well, but if I am going to to processing the detector 67, I get:

Loading reference objects from region bounded by [178.95980960, 179.17247366], [36.54467071, 37.03354999] RA Dec


as you can see, the reference is on the left of the image, and if I turn the config:

config.astromRefObjLoader.pixelMargin=250 from 250 to 1000

the refernce(in the picture is the red) only change in the left,


and I still get a wrong match:

For other detector, things worse, for example: for detector 76:

calibrate INFO: Loading reference objects from region bounded by [178.82656072, 179.04041542], [36.96779216, 37.45667326] RA Dec
calibrate INFO: Loaded 0 reference objects
calibrate.astrometry.referenceSelector INFO: Selected 0/0 references

and for detector 77, things worse:

calibrate INFO: Loading reference objects from region bounded by [178.69353873, 178.90798618], [36.96752792, 37.45663713] RA Dec
ctrl.mpexec.singleQuantumExecutor ERROR: Execution of task 'calibrate' on quantum {instrument: 'WFST', detector: 77, visit: 7, ...} failed. Exception RuntimeError: No reference tables could be found for input region
ctrl.mpexec.mpGraphExecutor ERROR: Task <TaskDef(CalibrateTask, label=calibrate) dataId={instrument: 'WFST', detector: 77, visit: 7, ...}> failed; processing will continue for remaining tasks.
Traceback (most recent call last):
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/ctrl_mpexec/g6727979600+15d2600a0d/python/lsst/ctrl/mpexec/mpGraphExecutor.py", line 363, in _executeQuantaInProcess
    self.quantumExecutor.execute(qnode.taskDef, qnode.quantum, butler)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/ctrl_mpexec/g6727979600+15d2600a0d/python/lsst/ctrl/mpexec/singleQuantumExecutor.py", line 174, in execute
    self.runQuantum(task, quantum, taskDef, butler)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/ctrl_mpexec/g6727979600+15d2600a0d/python/lsst/ctrl/mpexec/singleQuantumExecutor.py", line 464, in runQuantum
    task.runQuantum(butlerQC, inputRefs, outputRefs)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/pipe_tasks/gf1799c5b72+6048f86b6d/python/lsst/pipe/tasks/calibrate.py", line 635, in runQuantum
    outputs = self.run(**inputs)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/pipe_base/g5c83ca0194+970dd35637/python/lsst/pipe/base/timer.py", line 181, in wrapper
    res = func(self, *args, **keyArgs)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/pipe_tasks/gf1799c5b72+6048f86b6d/python/lsst/pipe/tasks/calibrate.py", line 735, in run
    astromRes = self.astrometry.run(
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/pipe_base/g5c83ca0194+970dd35637/python/lsst/pipe/base/timer.py", line 181, in wrapper
    res = func(self, *args, **keyArgs)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/meas_astrom/gaf95d0f0f9+8df549a9bd/python/lsst/meas/astrom/astrometry.py", line 178, in run
    res = self.solve(exposure=exposure, sourceCat=sourceCat)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/pipe_base/g5c83ca0194+970dd35637/python/lsst/pipe/base/timer.py", line 181, in wrapper
    res = func(self, *args, **keyArgs)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/meas_astrom/gaf95d0f0f9+8df549a9bd/python/lsst/meas/astrom/astrometry.py", line 224, in solve
    loadRes = self.refObjLoader.loadPixelBox(
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/meas_algorithms/g8d527e0710+15f63ea384/python/lsst/meas/algorithms/loadReferenceObjects.py", line 408, in loadPixelBox
    return self.loadRegion(outerSkyRegion, filtFunc=_filterFunction, epoch=epoch, filterName=filterName)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/meas_algorithms/g8d527e0710+15f63ea384/python/lsst/meas/algorithms/loadReferenceObjects.py", line 476, in loadRegion
    raise RuntimeError("No reference tables could be found for input region")
RuntimeError: No reference tables could be found for input region
ctrl.mpexec.mpGraphExecutor INFO: Executed 2 quanta successfully, 1 failed and 0 remain out of total 3 quanta.
lsst.daf.butler.cli.utils ERROR: Caught an exception, details are in traceback:
Traceback (most recent call last):
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/ctrl_mpexec/g6727979600+15d2600a0d/python/lsst/ctrl/mpexec/cli/cmd/commands.py", line 107, in run
    script.run(qgraphObj=qgraph, **kwargs)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/ctrl_mpexec/g6727979600+15d2600a0d/python/lsst/ctrl/mpexec/cli/script/run.py", line 171, in run
    f.runPipeline(qgraphObj, taskFactory, args)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/ctrl_mpexec/g6727979600+15d2600a0d/python/lsst/ctrl/mpexec/cmdLineFwk.py", line 681, in runPipeline
    executor.execute(graph, butler)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/ctrl_mpexec/g6727979600+15d2600a0d/python/lsst/ctrl/mpexec/mpGraphExecutor.py", line 302, in execute
    self._executeQuantaInProcess(graph, butler)
  File "/home/yu/lsst_stack/23.0.1/stack/miniconda3-py38_4.9.2-0.8.1/Linux64/ctrl_mpexec/g6727979600+15d2600a0d/python/lsst/ctrl/mpexec/mpGraphExecutor.py", line 396, in _executeQuantaInProcess
    raise MPGraphExecutorError("One or more tasks failed during execution.")
lsst.ctrl.mpexec.mpGraphExecutor.MPGraphExecutorError: One or more tasks failed during execution.

And if I change a sky region, things occur again, for example:
The reference is all on the bottom:


If I turn the

config.astromRefObjLoader.pixelMargin=1500

Then I get:


Seems like a line prevent the stars to across it.
What caused this, how to fix that?
Thank you!

Can you link to your full camera definition? The code listed here defines the detectors/amplifiers in focal plane coordinates (mm), but I don’t see anything that connects that with the sky. I’m not sure as written that I understand how you’re getting any reference catalog information.

Thank you!

In the Translator:

    def to_tracking_radec(self):
        # Docstring will be inherited. Property defined in properties.py
        radec = SkyCoord(self._header["RA2000"], self._header["DEC2000"],
                         frame="icrs", unit=(u.deg, u.deg), # u.hourangle, u.deg to u.deg 2023 07-04
                         obstime=self.to_datetime_begin(), location=self.to_location())
        self._used_these_cards("RA2000", "DEC2000")
        return radec

the header of the of image that do with the ra,dec is:

RA2000  =                180.0                                                  
DEC2000 =                 36.0

And my image is in size (9216,9232), And the code related to a CCD is:

config.detectorList[4] = lsst.afw.cameraGeom.cameraConfig.DetectorConfig()
# y0 of pixel bounding box
config.detectorList[4].bbox_y0 = 0

# y1 of pixel bounding box
config.detectorList[4].bbox_y1 = 9231

# x1 of pixel bounding box
config.detectorList[4].bbox_x1 = 9215

# x0 of pixel bounding box
config.detectorList[4].bbox_x0 = 0

# Name of detector slot
config.detectorList[4].name = '4'

# Pixel size in the x dimension in mm
config.detectorList[4].pixelSize_x = 0.01

# Name of native coordinate system
config.detectorList[4].transformDict.nativeSys = 'Pixels'

config.detectorList[4].transformDict.transforms = None
# x position of the reference point in the detector in pixels in transposed coordinates.

config.detectorList[4].refpos_x = 4607.5

# y position of the reference point in the detector in pixels in transposed coordinates.
config.detectorList[4].refpos_y = 4615.5


# Pixel size in the y dimension in mm
config.detectorList[4].pixelSize_y = 0.01

# Detector type: SCIENCE=0, FOCUS=1, GUIDER=2, WAVEFRONT=3
config.detectorList[4].detectorType = 0

# x offset from the origin of the camera in mm in the transposed system.
config.detectorList[4].offset_x = 0

# y offset from the origin of the camera in mm in the transposed system.
config.detectorList[4].offset_y = 0

# Transpose the pixel grid before orienting in focal plane?
config.detectorList[4].transposeDetector = False

# yaw (rotation about z) of the detector in degrees. This includes any
# necessary rotation to go from detector coordinates to camera coordinates
# after optional transposition.
config.detectorList[4].yawDeg = 0.0

# roll (rotation about x) of the detector in degrees
config.detectorList[4].rollDeg = 0.0

# Serial string associated with this specific detector
config.detectorList[4].serial = '4'

# pitch (rotation about y) of the detector in degrees
config.detectorList[4].pitchDeg = 0.0

# ID of detector slot
config.detectorList[4].id = 4

I can load reference catalog successfully,
but things change if I want to split the image, for example, I change the image into (9216/8,9232/2) 16 parts, and the header of each part didn’t change, it remains, and the value is the ra_dec for refpos or offset

RA2000  =                180.0                                                  
DEC2000 =                 36.0

but I need to change the code related the CCD to counter for the change:

i = 4
for j in range(0,16):
    k = j + i*16
    config.detectorList[k] = lsst.afw.cameraGeom.cameraConfig.DetectorConfig()
    # y0 of pixel bounding box
    config.detectorList[k].bbox_y0 = 0
    
    # y1 of pixel bounding box
    config.detectorList[k].bbox_y1 = 4631
    
    # x1 of pixel bounding box
    config.detectorList[k].bbox_x1 = 1151
    
    # x0 of pixel bounding box
    config.detectorList[k].bbox_x0 = 0
    
    # Name of detector slot
    config.detectorList[k].name = f'4_16_{j}'
    
    # Pixel size in the x dimension in mm
    config.detectorList[k].pixelSize_x = 0.01
    
    # Name of native coordinate system
    config.detectorList[k].transformDict.nativeSys = 'Pixels'
    
    config.detectorList[k].transformDict.transforms = None
    
    # x position of the reference point in the detector in pixels in transposed coordinates.
    config.detectorList[k].refpos_x = 4607.5

    # y position of the reference point in the detector in pixels in transposed coordinates.
    config.detectorList[k].refpos_y = 4615.5
    
    # Pixel size in the y dimension in mm
    config.detectorList[k].pixelSize_y = 0.01
    
    # Detector type: SCIENCE=0, FOCUS=1, GUIDER=2, WAVEFRONT=3
    config.detectorList[k].detectorType = 0
    if j//8 ==0:
        # x offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_x = 0 + 0.01*1152*(j%8)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 0 
    else:
        config.detectorList[k].offset_x = 0 + 0.01*1152*(j%8)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 0 + 0.01*4616
    # Transpose the pixel grid before orienting in focal plane?
    config.detectorList[k].transposeDetector = False
    
    # yaw (rotation about z) of the detector in degrees. This includes any
    # necessary rotation to go from detector coordinates to camera coordinates
    # after optional transposition.
    config.detectorList[k].yawDeg = 0.0
    
    # roll (rotation about x) of the detector in degrees
    config.detectorList[k].rollDeg = 0.0
    
    # Serial string associated with this specific detector
    config.detectorList[k].serial = f'4_16_{j}'
    
    # pitch (rotation about y) of the detector in degrees
    config.detectorList[k].pitchDeg = 0.0
    
    # ID of detector slot
    config.detectorList[k].id = k

Here I use the loop to change the offset to control the change after split, but then the reference can only load when the j is small, for example 1,2,3

    if j//8 ==0:
        # x offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_x = 0 + 0.01*1152*(j%8)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 0 
    else:
        config.detectorList[k].offset_x = 0 + 0.01*1152*(j%8)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 0 + 0.01*4616

And other code is

import lsst.afw.cameraGeom.cameraConfig
import numpy as np
assert type(config) == lsst.afw.cameraGeom.cameraConfig.CameraConfig, 'config is of type %s.%s instead of lsst.afw.cameraGeom.cameraConfig.CameraConfig' % (
    type(config).__module__, type(config).__name__)

# Plate scale of the camera in arcsec/mm
config.plateScale = 33
# Name of native coordinate system
config.transformDict.nativeSys = 'FocalPlane'
config.transformDict.transforms = {}
config.transformDict.transforms['FieldAngle'] = lsst.afw.geom.transformConfig.TransformConfig()
config.transformDict.transforms['FieldAngle'].transform['affine'].translation = [0.0, 0.0]
config.transformDict.transforms['FieldAngle'].transform['affine'].linear = [0.0001599883644825831, 0.0, 0.0, 0.0001599883644825831]
config.transformDict.transforms['FieldAngle'].transform.name = 'affine'

which I think have nothing to do with the question.
I guess I should use the way like HSC? use the center of every CCD as refpos (which is the way when I didn’t spilt the CCD) and change the RA2000 and DEC2000 of the splited image, and don’t use the loop.

I think I see the underlying issue: this code is interpreting what afw and obs_base consider “Amplifiers” as “Detectors.” The afw cameraGeom model defines a hierarchical system where the Camera is composed of at least one Detector (corresponding to a single physical device), and that that Detector is composed of at least one Amplifier (corresponding to a single readout channel on the physical Detector). It looks like you have a similar Detector layout to the LSSTCam detectors: two rows that each contain eight Amplifiers.
The clearest example on how this hierarchical system works is presented in https://github.com/lsst/afw/blob/main/python/lsst/afw/cameraGeom/testUtils.py which is used to construct simulated cameras for testing. The class DetectorWrapper creates a test Detector with some default settings and a set of identical Amplifiers, and the CameraWrapper creates a test Camera comprised of Detectors and Amplifiers, as defined in the tabular data stored in https://github.com/lsst/afw/tree/main/python/lsst/afw/cameraGeom/testData .

Returning to your code, I think you’ve taken each readout channel as a separate Detector, and that is causing the issues. In this case, you should be able to iterate over i, populating each with a Detector, and then iterate over j to fill that Detector with Amplifier objects. Putting together an example:

import lsst.afw.cameraGeom as cameraGeom
import lsst.afw.geom as afwGeom
import lsst.geom as geom

import numpy as np

# Due to C++ backend code, we must work with Builder objects
cameraBuilder = cameraGeom.Camera.Builder("WFST-test-camera")
for i in range(0,4):  # Iterate over detectors
    
    bbox = geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(9216, 9232))
    # Create a new detector in the existing camera:
    detectorBuilder = cameraBuilder.add(f"WFST-test-detector-{i}", i)
    detectorBuilder.setType(cameraGeom.DetectorType.SCIENCE)  # A SCIENCE detector
    detectorBuilder.setPhysicalType("CCD")  # The type of detector technology.
    detectorBuilder.setSerial(f"abcd-1234{i}")  # The physical device serial number.
    detectorBuilder.setBBox(bbox)  # Set full size of the detector array.
    detectorBuilder.setPixelSize(geom.Extent2D(0.01, 0.01))  # Pixel size.
    
    # The default Orientation() puts the normal of the detector along the optical path.
    detectorBuilder.setOrientation(cameraGeom.Orientation())
    
    # Populate this detector with amplifiers:
    numberColumnsRows = (8, 2)  # 8*2=16.
    amplifierBoxes = afwGeom.testUtils.BoxGrid(bbox, numberColumnsRows)
    for jcolumns in range(numberColumnsRows[0]):
        for jrows in range(numberColumnsRows[1]):
            ampBuilder = cameraGeom.Amplifier.Builder()
            ampBuilder.setName(f"Amplifier {jcolumns}_{jrows}")
            ampBuilder.setBBox(amplifierBoxes[jcolumns, jrows])
            ampBuilder.setGain(1.0)
            ampBuilder.setReadNoise(5.0)
            # There are other bboxes that need to be set.
            # https://github.com/lsst/obs_lsst/blob/main/policy/cameraHeader.yaml#L34
            # gives a good explanation of what each field represents.  The individual
            # methods are listed here:
            # https://github.com/lsst/afw/blob/main/include/lsst/afw/cameraGeom/Amplifier.h#L386
            
            # Attach this amplifier to the parent detector:
            detectorBuilder.append(ampBuilder)

# Copy transformDict information from supplied code.
# Use lsst.obs.base.yamlCamera.makeTransformDict() as starting point
    
# I believe this is only used for radial transforms
# config.plateScale = 33 
# Name of native coordinate system
# This must be FOCAL_PLANE, so we'll skip it.
# transformDict.nativeSys = 'FocalPlane'
transforms = {}
transforms['FieldAngle'] = afwGeom.makeTransform(
geom.AffineTransform(np.array([[0.0001599883644825831, 0.0],
                               [0.0, 0.0001599883644825831]]),
                     np.array([0.0, 0.0]))
)
for toSys, transform in transforms.items():
    toSys = cameraGeom.CameraSys(toSys)
    cameraBuilder.setTransformFromFocalPlaneTo(toSys, transform)
    
# Finish camera
camera = cameraBuilder.finish()

I think a camera defined in this way will let your reference catalog search find sources covering the full Detector.

Thank you very much for your reply!
But what I want to do is to take each readout channel as a separate Detector, the primary definition of the unsplitted is:

config.detectorList[4].name = '4'
config.detectorList[4].serial = '4'

And the detectort use the file 4.fits, in that file it describe the detector composed with 16 amplifiers,
And I divide them into 16 parts

config.detectorList[k].serial = f'4_16_{j}'
config.detectorList[k].name = f'4_16_{j}'

The j range from (0,16) and it point to the file 4_16_{j}.fits, and in that file, there are only one amplifier.
And the reference can’t load properly if I divide the CCD into 8 parts and 16 parts,

and if I don’t divide the CCD into such small parts, I divide the CCD into 4 parts this time, and use the similiar loop, the reference will load successfully, this is the defintion if I divide the CCD into 4 parts:

i = 0
for j in range(0,4):
    k = j + i*4
    
    config.detectorList[k] = lsst.afw.cameraGeom.cameraConfig.DetectorConfig()
    # y0 of pixel bounding box
    config.detectorList[k].bbox_y0 = 0
    
    # y1 of pixel bounding box
    config.detectorList[k].bbox_y1 = 4631
    
    # x1 of pixel bounding box
    config.detectorList[k].bbox_x1 = 4607
    
    # x0 of pixel bounding box
    config.detectorList[k].bbox_x0 = 0
    
    # Name of detector slot
    config.detectorList[k].name = f'4_4_{j}'
    
    # Pixel size in the x dimension in mm
    config.detectorList[k].pixelSize_x = 0.01
    
    # Name of native coordinate system
    config.detectorList[k].transformDict.nativeSys = 'Pixels'
    
    config.detectorList[k].transformDict.transforms = None
    # x position of the reference point in the detector in pixels in transposed coordinates.
    
    config.detectorList[k].refpos_x = 4607.5
    
    # y position of the reference point in the detector in pixels in transposed coordinates.
    config.detectorList[k].refpos_y = 4615.5
    
    # Pixel size in the y dimension in mm
    config.detectorList[k].pixelSize_y = 0.01
    
    # Detector type: SCIENCE=0, FOCUS=1, GUIDER=2, WAVEFRONT=3
    config.detectorList[k].detectorType = 0
    if j//2 == 0:
        
        # x offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_x = -94.2 +  0.01*4608*(j%2)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 100.22
    else:
        # x offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_x = -94.2 +  0.01*4608*(j%2)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 100.22 + 4632*0.01
    # Transpose the pixel grid before orienting in focal plane?
    config.detectorList[k].transposeDetector = False
    
    # yaw (rotation about z) of the detector in degrees. This includes any
    # necessary rotation to go from detector coordinates to camera coordinates
    # after optional transposition.
    config.detectorList[k].yawDeg = 0.0
    
    # roll (rotation about x) of the detector in degrees
    config.detectorList[k].rollDeg = 0.0
    
    # Serial string associated with this specific detector
    config.detectorList[k].serial = f'4_4_{j}'
    
    # pitch (rotation about y) of the detector in degrees
    config.detectorList[k].pitchDeg = 0.0
    
    # ID of detector slot
    config.detectorList[k].id = k

The only change here is the if:
from

    if j//8 ==0:
        # x offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_x = -94.2 + 0.01*1152*(j%8)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 100.22
    else:
        # x offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_x = -94.2 + 0.01*1152*(j%8)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 100.22 + 0.01*4632

to

    if j//2 == 0:
        
        # x offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_x = -94.2 +  0.01*4608*(j%2)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 100.22
    else:
        # x offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_x = -94.2 +  0.01*4608*(j%2)
        
        # y offset from the origin of the camera in mm in the transposed system.
        config.detectorList[k].offset_y = 100.22 + 4632*0.01

And also the file it points to the file descirbe the amplifier:

config.detectorList[k].serial = f'4'
config.detectorList[k].name = f'4'

to

config.detectorList[k].serial = f'4_4_{j}'
config.detectorList[k].name = f'4_4_{j}'

I wonder why the reference will load successfully if I divide the CCD into 4 parts this time, and use the similiar loop.

Thank you! And sorry for wasting your time, I have figure out this question, it was caused by not refresh my butler folder, for example, if I create a butler and a following command:

butler create {butler_path}
butler register-instrument {butler_path} wfst._instrument.WFSTCamera
butler write-curated-calibrations {butler_path} WFST

and then I modified the code in camera.py, but without creating a new butler folder and run the above command. In the old folder, if I begin to processing the data, not everything changed to the status I modified later, and the reference will not load correctly.