@@ -44,7 +44,7 @@ In this project, we will compare 2 graph abstractions and 3 algorithms for seam
### Interface
`Picture`
: A representation of a fixed-size digital image where the color of each pixel is an `int`.
: A representation of a fixed-size digital image where the color of each pixel is an `int`. In image processing, pixel (*x*, *y*) refers to the pixel in column *x* and row *y*, with pixel (0, 0) at the upper-left corner and pixel (*W* − 1, *H* − 1) at the lower-right corner. Note that this is the opposite of the standard linear algebra notation where (*i*, *j*) refers to row *i* and column *j* and (0, 0) is at the lower-left corner.
Seam carving is a 3-step algorithm, each represented as a Java `class` or `interface`.
...
...
@@ -52,7 +52,7 @@ Seam carving is a 3-step algorithm, each represented as a Java `class` or `inter
: The energy function defines the importance of a given pixel in a picture. The higher the energy, the greater its importance. The video at [1:45](https://youtu.be/6NcIJXTlugc?t=105) depicts bright, high-energy pixels around object boundaries.
`SeamFinder`
: The seam finding algorithm will avoid high-energy pixels by searching for a **horizontal seam** with the minimum energy path. Continuing from [1:45](https://youtu.be/6NcIJXTlugc?t=105), the video identifies a vertical seam that can be removed from the picture without noticeably affecting the content.
: The seam finding algorithm will avoid high-energy pixels by searching for a **horizontal seam** with the minimum energy path. The horizontal seam is returned as a list of integers representing the y-coordinates. Continuing from [1:45](https://youtu.be/6NcIJXTlugc?t=105), the video identifies a vertical seam that can be removed from the picture without noticeably affecting the content.
`SeamCarver`
: The seam carving algorithm combines an energy function with the seam finder to identify and remove the most unnoticeable seams from an picture. This class handles vertical seams by transposing the picture before calling `SeamFinder`.
...
...
@@ -68,26 +68,21 @@ The focus of this project is on comparing implementations for `SeamFinder`. In o
`graphs.ExtrinsicMinPQ<T>`
: Priority queue interface for `DijkstraSolver`.
The generic type `V` is used throughout the `graphs` package to represent the vertex data type.
The generic type `V` is used throughout the `graphs` package to represent the vertex data type. All vertices will be of the interface type `Node`, which provides a single method called `neighbors` that accepts a `Picture` and an `EnergyFunction`. Rather than defining all of the graph logic for every type of node in the `Graph.neighbors` method, this approach allows us to define specialized behavior on a per-node basis by implementing `Node.neighbors`.
`Node`
: An [**adapter**](https://en.wikipedia.org/wiki/Adapter_pattern) for the `Graph` interface that delegates responsibility for `neighbors` to each individual `Node` rather than the entire `Graph`. This interface allows different types of nodes to provide specialized behavior for `neighbors`.
: An [**adapter**](https://en.wikipedia.org/wiki/Adapter_pattern) for the `Graph` interface that delegates responsibility for `neighbors` to each individual `Node` rather than the entire `Graph`.
### Reference implementation
`AdjacencyListSeamFinder(DijkstraSolver::new)`
: An adjacency list-like `Graph` representation combined with Dijkstra's shortest paths algorithm.
The `AdjacencyListSeamFinder` class contains a `PixelGraph` class that implements `Graph<Node>`. The `PixelGraph` class defines 3 `Node` implementations, each with their own algorithms for determining how to compute `neighbors`.
`source`
: The **source** node contains 0 incoming edges and `picture.height()` outgoing edges to each `Pixel` in the first column of the picture. The weight for each outgoing edge represents the energy of the corresponding pixel in the first column.
`Pixel`
: Represents an (*x*, *y*) coordinate pixel in the underlying picture with directed edges to its right-up, right-middle, and right-down neighbors. Most pixels have 3 adjacent neighbors except for pixels at the boundary of the picture that only have 2 adjacent neighbors. The weight of an edge `from`---`to` represents the energy of the `to` pixel.
`sink`
: The **sink** node contains `picture.height()` incoming edges and 0 outgoing edges: no neighbors.
: An adjacency list-like `Graph` representation combined with Dijkstra's shortest paths algorithm. The `AdjacencyListSeamFinder` class contains a `PixelGraph` class that implements `Graph<Node>`. Most of the logic for initializing the adjacency list representation is in the `PixelGraph` constructor. The `PixelGraph` class defines 3 `Node` implementations, each with their own algorithms for determining how to compute `neighbors`.
: `source`
: The **source** node contains 0 incoming edges and `picture.height()` outgoing edges to each `Pixel` in the first column of the picture. The weight for each outgoing edge represents the energy of the corresponding pixel in the first column.
: `Pixel`
: Represents an (*x*, *y*) coordinate pixel in the underlying picture with directed edges to its right-up, right-middle, and right-down neighbors. Most pixels have 3 adjacent neighbors except for pixels at the boundary of the picture that only have 2 adjacent neighbors. The weight of an edge `from`---`to` represents the energy of the `to` pixel.
: `sink`
: The **sink** node contains `picture.height()` incoming edges and 0 outgoing edges: no neighbors.
The `AdjacencyListSeamFinder.findSeam` method constructs a `PixelGraph`, runs the shortest paths solver on the graph from the source node, and uses the path to the sink node to find a horizontal seam with the minimum energy path. Since the shortest paths solver returns the entire path, the source and sink nodes need to be ignored in the final `result` with `seam.subList(1, seam.size() - 1)`.
...
...
@@ -100,7 +95,7 @@ The source and sink nodes are [anonymous classes](https://docs.oracle.com/javase
Design and implement an alternative graph representation and graph algorithm for solving shortest paths in a **directed acyclic graph**.
`GenerativeSeamFinder`
: Rather than precomputing and storing the neighbors for every single `Pixel` in a 2-D array, this graph representation computes the `neighbors` for each pixel on demand to reduce memory usage. Identify the relevant portions of the `AdjacencyListSeamFinder.PixelGraph` class and modify it so that the `PixelGraph.pixels` and `Pixel.neighbors` fields are longer needed.
: Rather than precomputing and storing the neighbors for every single `Pixel` in a 2-D array, this graph representation computes the `neighbors` for each pixel on demand to reduce memory usage. Identify the relevant portions of the `AdjacencyListSeamFinder.PixelGraph` class and modify it so that the `PixelGraph.pixels` and `Pixel.neighbors` fields are no longer needed.
`graphs.ToposortDAGSolver`
: Computes the shortest paths in a directed acyclic graph using the reduction to topological sorting presented in the lesson.
...
...
@@ -109,10 +104,11 @@ Design and implement an alternative graph representation and graph algorithm for
1. For each node in reverse DFS post-order, apply Dijkstra's edge relaxation step: if the distance to the neighboring node using the given edge is less than the `distTo` value to the neighboring node, update `distTo` and `edgeTo` accordingly.
`DynamicProgrammingSeamFinder`
: A [dynamic programming seam finding algorithm](https://avikdas.com/2019/05/14/real-world-dynamic-programming-seam-carving.html#bottom-up-implementation), which is similar to the `ToposortDAGSolver` except that it operates directly on the `Picture` rather than through an abstract `Graph` representation.
: 1. Initialize a 2-D `double[][]` with dimensions `picture.width()` × `picture.height()` to represent the cost of the **minimum energy path** from the left edge to each pixel.
1. Iteratively compute the minimum energy path to each pixel starting from the left edge of the picture and working towards the right edge by considering the preceding path costs: the left-up, left-middle, and left-down predecessors.
1. Compute the shortest path by starting from the right edge and working back towards the left edge. Follow the minimum-cost predecessor and add its index to a list. [`Collections.reverse`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Collections.html#reverse(java.util.List)) the list to obtain the horizontal seam.
: A [dynamic programming seam finding algorithm](https://avikdas.com/2019/05/14/real-world-dynamic-programming-seam-carving.html#bottom-up-implementation). This algorithm solves the problem by considering pixels from left to right similar to the `ToposortDAGSolver` except that it operates directly on the `Picture` rather than through an abstract `Graph` representation.
: 1. Initialize a 2-D `double[picture.width()][picture.height()]` to represent the cost of the **minimum energy path** from the left edge to each pixel.
1. Iteratively compute the minimum energy path to each pixel starting from the left edge of the picture and working towards the right edge by considering each pixel's preceding path costs: the **left-up**, **left-middle**, and **left-down** pixels.
1. Compute the shortest path by starting from the right edge and working back towards the left edge, adding each minimum-cost **y-coordinate** to a list.
1.[`Collections.reverse`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Collections.html#reverse(java.util.List)) the list to obtain the horizontal seam.
**Video**{: .label .label-purple }
: Explain one part of the project implementation that you're particularly proud of developing.
...
...
@@ -122,10 +118,18 @@ Design and implement an alternative graph representation and graph algorithm for
**Open Terminal**<svgclass="inline-icon"xmlns="http://www.w3.org/2000/svg"viewBox="0 0 100 82"fill-rule="evenodd"stroke-linejoin="round"stroke-miterlimit="2"id="terminal"><pathd="M44.518 70.139H100v11.097H44.518z"></path><pathd="M7.845 73.347L0 65.502l28.826-28.828L0 7.845 7.845 0l36.673 36.674L7.845 73.347z"fill-rule="nonzero"></path></svg> and enter the following command to compile and run the `SeamCarver` class. Use the keyboard shortcut <kbd>Ctrl+C</kbd> to stop program execution at any time. Once the `SeamCarver` class has finished execution, the `rm` command removes the compiled class files.
By default, the `SeamCarver` class uses the `AdjacencyListSeamFinder` together with the `DijkstraSolver`. Modify the class to test your own implementation. The `data` folder includes example `png` images and corresponding expected `txt` results.
By default, the `SeamCarver` class uses the `AdjacencyListSeamFinder` together with the `DijkstraSolver`. Modify the class to test your own implementation.
For debugging, use print statements to output the state of important variables. Study this 4-minute video on [debugging with print statements](https://youtu.be/EnJhV2j8YR0). Note how they spend almost all of the time diagnosing the problem and developing a thorough explanation for why the program is not working as expected. The actual bugfix only takes a few seconds of typing. This is normal practice for all programmers, though programmers who have seen similar bugs before will be able to identify and resolve problems more quickly.
To compare several implementations at once, run the provided `ImageProcessingMultiTest`, which validates the implementations against the expected minimum energy path cost. If some of your implementations are not ready for testing, comment them out of the `implementations` map.