diff --git a/go.mod b/go.mod index 69878e4..b2ca504 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,6 @@ go 1.17 require ( github.com/disintegration/imaging v1.6.2 + github.com/pkg/errors v0.9.1 golang.org/x/image v0.0.0-20211028202545-6944b10bf410 ) diff --git a/go.sum b/go.sum index 6abdd3e..8a9acf6 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= diff --git a/network.go b/network.go index 1883eb0..f8ec962 100644 --- a/network.go +++ b/network.go @@ -7,9 +7,14 @@ package darknet // #include "network.h" import "C" import ( - "errors" + "fmt" + "io/ioutil" + "os" + "syscall" "time" "unsafe" + + "github.com/pkg/errors" ) // YOLONetwork represents a neural network using YOLO. @@ -53,6 +58,72 @@ func (n *YOLONetwork) Init() error { return nil } +/* EXPERIMENTAL */ +/* + By default AlexeyAB's Darknet doesn't export any functions in darknet.h to give ability to create network from scratch via code. + So I can't modify `parse_network_cfg_custom` to load `list *sections = read_cfg(filename);` from memory. + + So, the point of this method is to be able create network configuration via Golang and then pass it to `C.load_network` +*/ +func (n *YOLONetwork) InitFromDefinedCfg() error { + wFile := C.CString(n.WeightsFile) + defer C.free(unsafe.Pointer(wFile)) + /* Prepare network sections via Go */ + /* + instead of using: + wFile := C.CString(n.WeightsFile) + defer C.free(unsafe.Pointer(wFile)) + We call `load_network` that takes the first char* parameter (representing a file path to network configuration) with a Go function that takes an in-memory file + */ + cfgBytes, err := os.ReadFile(n.NetworkConfigurationFile) + if err != nil { + return errors.Wrap(err, "Can't read file bytes") + } + // Create a temporary file. + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + return errors.Wrap(err, "Can't create temporary file") + } + defer os.Remove(tmpFile.Name()) + fmt.Println("here", tmpFile.Name()) + // Write the file content to the temporary file. + if _, err := tmpFile.Write(cfgBytes); err != nil { + return errors.Wrap(err, "Can't write network's configuration into temporary file") + } + defer tmpFile.Close() + // Open the temporary file. + fd, err := syscall.Open(tmpFile.Name(), syscall.O_RDWR, 0) + if err != nil { + return errors.Wrap(err, "Can't re-open temporary file") + } + defer syscall.Close(fd) + // Create a memory mapping of the file. + addr, err := syscall.Mmap(fd, 0, len(cfgBytes), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + if err != nil { + return errors.Wrap(err, "Can't mmap on temporary file") + } + + // GPU device ID must be set before `load_network()` is invoked. + C.cuda_set_device(C.int(n.GPUDeviceIndex)) + nCfg := C.CString(tmpFile.Name()) + defer C.free(unsafe.Pointer(nCfg)) + n.cNet = C.load_network(nCfg, wFile, 0) + if n.cNet == nil { + return errUnableToInitNetwork + } + C.srand(2222222) + n.hierarchalThreshold = 0.5 + n.nms = 0.45 + metadata := C.get_metadata(nCfg) + n.Classes = int(metadata.classes) + n.ClassNames = makeClassNames(metadata.names, n.Classes) + // Unmap the memory-mapped file. + if err := syscall.Munmap(addr); err != nil { + return errors.Wrap(err, "Can't revert mmap") + } + return nil +} + // Close and release resources. func (n *YOLONetwork) Close() error { if n.cNet == nil {